Skip to content

Commit

Permalink
perf: overall improvements and optimizations
Browse files Browse the repository at this point in the history
  • Loading branch information
Anidetrix committed May 20, 2020
1 parent d81b6ad commit a460dd0
Show file tree
Hide file tree
Showing 17 changed files with 137 additions and 144 deletions.
64 changes: 32 additions & 32 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/// <reference types="./typings/fibers" />
/// <reference types="./typings/less" />
/// <reference types="./typings/postcss-modules" />
/// <reference types="./typings/sass" />
/// <reference types="./typings/stylus" />
/// <reference types="./shims/postcss-modules" />
/// <reference types="./shims/fibers" />
/// <reference types="./shims/sass" />
/// <reference types="./shims/less" />
/// <reference types="./shims/stylus" />

import { createFilter } from "@rollup/pluginutils";

Expand All @@ -24,23 +24,23 @@ import {
import { mm } from "./utils/sourcemap";
import {
inferOption,
inferResolverOption,
inferModeOption,
inferHandlerOption,
ensureUseOption,
ensurePCSSOption,
ensurePCSSPlugins,
ensureUseOption,
inferModeOption,
} from "./utils/options";

export default (options: Options = {}): Plugin => {
const filter = createFilter(options.include, options.exclude);

const postcssLoaderOpts: PostCSSLoaderOptions = {
const postcssOpts: PostCSSLoaderOptions = {
...inferModeOption(options.mode),

minimize: inferOption(options.minimize, false),
config: inferOption(options.config, {}),
import: inferResolverOption(options.import, options.alias),
url: inferResolverOption(options.url, options.alias),
import: inferHandlerOption(options.import, options.alias),
url: inferHandlerOption(options.url, options.alias),
modules: inferOption(options.modules, false),

to: options.to,
Expand All @@ -57,9 +57,9 @@ export default (options: Options = {}): Plugin => {
};

const loaders = new Loaders({
use: [["postcss", postcssLoaderOpts], ...ensureUseOption(options.use, options), "sourcemap"],
use: [["postcss", postcssOpts], ...ensureUseOption(options.use, options), "sourcemap"],
loaders: options.loaders ?? [],
extensions: postcssLoaderOpts.extensions,
extensions: postcssOpts.extensions,
});

const isSupported = (id: string): boolean => filter(id) && loaders.isSupported.call(loaders, id);
Expand Down Expand Up @@ -107,7 +107,7 @@ export default (options: Options = {}): Plugin => {
for (const [fileName, source] of ctx.assets)
this.emitFile({ type: "asset", fileName, source });

if (postcssLoaderOpts.extract) {
if (postcssOpts.extract) {
res.extracted && extracted.set(id, res.extracted);
return {
code: res.code,
Expand All @@ -126,9 +126,10 @@ export default (options: Options = {}): Plugin => {
augmentChunkHash(chunk) {
if (extracted.size === 0) return;

const ids = Object.keys(chunk.modules)
.map(m => this.getModuleInfo(m))
.reduce<string[]>((acc, i) => [...acc, i.id, ...i.importedIds], []);
const ids = Object.keys(chunk.modules).reduce<string[]>((acc, id) => {
const i = this.getModuleInfo(id);
return [...acc, i.id, ...i.importedIds];
}, []);

const hashable = [...extracted.values()]
.filter(e => ids.includes(e.id))
Expand All @@ -151,8 +152,8 @@ export default (options: Options = {}): Plugin => {

const getExtractedData = (name: string, ids: string[]): ExtractedData => {
const fileName =
typeof postcssLoaderOpts.extract === "string"
? normalizePath(postcssLoaderOpts.extract).replace(/^\.[/\\]/, "")
typeof postcssOpts.extract === "string"
? normalizePath(postcssOpts.extract).replace(/^\.[/\\]/, "")
: normalizePath(`${name}.css`);

if (isAbsolutePath(fileName))
Expand Down Expand Up @@ -195,18 +196,17 @@ export default (options: Options = {}): Plugin => {
const getImports = (chunk: OutputChunk): string[] => {
const orderedIds = new Set<string>();

let ids = Object.keys(chunk.modules)
.map(m => this.getModuleInfo(m))
.reduce<string[]>((acc, i) => [...acc, i.id, ...i.importedIds], []);
let ids = Object.keys(chunk.modules).reduce<string[]>((acc, id) => {
const i = this.getModuleInfo(id);
return [...acc, i.id, ...i.importedIds];
}, []);

for (;;) {
ids = ids
.map(id => {
const i = this.getModuleInfo(id);
if (isSupported(i.id)) orderedIds.delete(i.id), orderedIds.add(i.id);
return i.importedIds;
})
.reduce<string[]>((acc, ids) => [...acc, ...ids], []);
ids = ids.reduce<string[]>((acc, id) => {
const i = this.getModuleInfo(id);
if (isSupported(i.id)) orderedIds.delete(i.id), orderedIds.add(i.id);
return [...acc, ...i.importedIds];
}, []);

if (ids.length === 0) return [...orderedIds];
}
Expand All @@ -216,7 +216,7 @@ export default (options: Options = {}): Plugin => {
const emittedMap = new Map<string, string[]>();
const chunks = Object.values(bundle).filter((c): c is OutputChunk => c.type === "chunk");
const entries = chunks.filter(c => c.isEntry || c.isDynamicEntry);
const multiFile = typeof postcssLoaderOpts.extract !== "string" && entries.length > 1;
const multiFile = typeof postcssOpts.extract !== "string" && entries.length > 1;

if (multiFile) {
const emitted = preserveModules ? chunks : entries;
Expand Down Expand Up @@ -252,9 +252,9 @@ export default (options: Options = {}): Plugin => {
}

// Perform minimization on the extracted file
if (postcssLoaderOpts.minimize) {
if (postcssOpts.minimize) {
const cssNanoOpts: cssnano.CssNanoOptions & postcss.ProcessOptions =
typeof postcssLoaderOpts.minimize === "object" ? postcssLoaderOpts.minimize : {};
typeof postcssOpts.minimize === "object" ? postcssOpts.minimize : {};

cssNanoOpts.from = res.name;
cssNanoOpts.to = res.name;
Expand Down
21 changes: 10 additions & 11 deletions src/loaders/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import PQueue from "p-queue";

import { Loader, LoaderContext, LoadersOptions, ObjectWithUnknownProps, Payload } from "../types";
import { Loader, LoaderContext, LoadersOptions, Payload } from "../types";
import postcssLoader from "./postcss";
import sourcemapLoader from "./sourcemap";
import sassLoader from "./sass";
import stylusLoader from "./stylus";
import lessLoader from "./less";

function matchFile(filepath: string, condition: Loader["test"]): boolean {
if (typeof condition === "function") return condition(filepath);
return Boolean(condition?.test(filepath));
function matchFile(file: string, condition: Loader["test"]): boolean {
if (typeof condition === "function") return condition(file);
return Boolean(condition?.test(file));
}

// This queue makes sure one thread is always available,
Expand All @@ -22,8 +22,8 @@ const workQueue = new PQueue({ concurrency: threadPoolSize - 1 });

export default class Loaders {
loaders: Loader[] = [];
use: [string, ObjectWithUnknownProps][];
test: (filepath: string) => boolean;
use: [string, object][];
test: (file: string) => boolean;

constructor(options: LoadersOptions) {
this.use = options.use.map(rule => {
Expand All @@ -32,8 +32,7 @@ export default class Loaders {
throw new TypeError("The rule in `use` option must be string or array!");
});

this.test = (filepath): boolean =>
options.extensions.some(ext => filepath.toLowerCase().endsWith(ext));
this.test = (file): boolean => options.extensions.some(ext => file.toLowerCase().endsWith(ext));

this.listLoader(postcssLoader);
this.listLoader(sourcemapLoader);
Expand All @@ -47,7 +46,7 @@ export default class Loaders {
return this.loaders.find(loader => loader.name === name);
}

listLoader<T extends ObjectWithUnknownProps>(loader: Loader<T>): void {
listLoader<T extends object>(loader: Loader<T>): void {
if (!this.use.some(rule => rule[0] === loader.name)) return;
if (this.getLoader(loader.name)) this.unlistLoader(loader.name);
this.loaders.push(loader as Loader);
Expand All @@ -57,8 +56,8 @@ export default class Loaders {
this.loaders = this.loaders.filter(loader => loader.name !== name);
}

isSupported(filepath: string): boolean {
return this.test(filepath) || this.loaders.some(loader => matchFile(filepath, loader.test));
isSupported(file: string): boolean {
return this.test(file) || this.loaders.some(loader => matchFile(file, loader.test));
}

async process(payload: Payload, context: LoaderContext): Promise<Payload> {
Expand Down
9 changes: 4 additions & 5 deletions src/loaders/less/importer.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import fs from "fs-extra";
import { FileManagerInterface, Less, LoadedFile, Plugin } from "less";
import resolveAsync from "../../utils/resolve-async";
import { getUrlOfPartial, normalizeUrl } from "../../utils/url";

const getStylesFileManager = (less: Less): FileManagerInterface =>
new (class extends less.AbstractFileManager implements FileManagerInterface {
const getStylesFileManager = (less: less.Less): less.FileManager =>
new (class extends less.AbstractFileManager implements less.FileManager {
supports(): boolean {
return true;
}

async loadFile(filename: string, basedir: string): Promise<LoadedFile> {
async loadFile(filename: string, basedir: string): Promise<less.File> {
const options = { basedir, extensions: [".less", ".css"] };

const url = normalizeUrl(filename);
Expand All @@ -27,7 +26,7 @@ const getStylesFileManager = (less: Less): FileManagerInterface =>
}
})();

const importer: Plugin = {
const importer: less.Plugin = {
install(less, pluginManager) {
pluginManager.addFileManager(getStylesFileManager(less));
},
Expand Down
10 changes: 5 additions & 5 deletions src/loaders/postcss/icss/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import postcss from "postcss";
import { extractICSS, replaceSymbols, replaceValueSymbols, Replacements } from "icss-utils";

import { extractICSS, replaceSymbols, replaceValueSymbols } from "icss-utils";
import { ModulesOptions } from "../modules";
import loadDefault, { Load } from "./load";
import resolve from "./resolve";

const name = "styles-icss";

export type InteroperableCSSOptions = {
export interface InteroperableCSSOptions {
load?: Load;
getReplacements?: (file: string, replacements: Replacements, out?: string) => void;
getReplacements?: ModulesOptions["getReplacements"];
extensions?: string[];
};
}

const plugin: postcss.Plugin<InteroperableCSSOptions> = postcss.plugin(
name,
Expand Down
10 changes: 5 additions & 5 deletions src/loaders/postcss/import/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ import valueParser from "postcss-value-parser";

import { normalizePath } from "../../../utils/path";

import resolveDefault, { Resolve } from "./resolve";
import resolveDefault, { ImportResolve } from "./resolve";

const name = "styles-import";

/** `@import` handler options */
export type ImportOptions = {
export interface ImportOptions {
/**
* Provide custom resolver for imports
* in place of the default one
*/
resolve?: Resolve;
resolve?: ImportResolve;
/**
* Aliases for import paths.
* Overrides the global `alias` option.
Expand All @@ -27,7 +27,7 @@ export type ImportOptions = {
* @default [".css", ".pcss", ".postcss", ".sss"]
*/
extensions?: string[];
};
}

const plugin: postcss.Plugin<ImportOptions> = postcss.plugin(
name,
Expand Down Expand Up @@ -72,7 +72,7 @@ const plugin: postcss.Plugin<ImportOptions> = postcss.plugin(
url = urlNode.value;
} else if (urlNode.type === "function") {
// Invalid function
if (urlNode.value.toLowerCase() !== "url") {
if (!/^url$/i.test(urlNode.value)) {
importRule.warn(res, `Invalid \`url\` function in \`${importRule.toString()}\``);
return;
}
Expand Down
15 changes: 12 additions & 3 deletions src/loaders/postcss/import/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,21 @@ import fs from "fs-extra";
import resolveAsync from "../../../utils/resolve-async";

/** File resolved by `@import` resolver */
export type ResolvedFile = { from: string; source: Uint8Array };
export interface ImportFile {
/** Absolute path to file */
from: string;
/** File source */
source: Uint8Array;
}

/** `@import` resolver */
export type Resolve = (url: string, basedir: string, extensions: string[]) => Promise<ResolvedFile>;
export type ImportResolve = (
url: string,
basedir: string,
extensions: string[],
) => Promise<ImportFile>;

const resolve: Resolve = async (url, basedir, extensions) => {
const resolve: ImportResolve = async (url, basedir, extensions) => {
const options = { basedir, extensions };
let from: string;
try {
Expand Down
8 changes: 3 additions & 5 deletions src/loaders/postcss/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ async function loadConfig(
const configPath =
typeof config === "object" && config.path ? path.resolve(config.path) : path.dirname(id);

const context: { [prop: string]: object | string } = {
const context: { [x: string]: object | string } = {
file: {
extname: path.extname(id),
dirname: path.dirname(id),
Expand All @@ -42,7 +42,7 @@ async function loadConfig(
};

return loadPostCSSConfig(context, configPath).catch(error => {
if (!(error.message as string).toLowerCase().includes("no postcss config found")) throw error;
if (!/no postcss config found/i.test(error.message)) throw error;
return {};
});
}
Expand Down Expand Up @@ -74,7 +74,7 @@ const loader: Loader<PostCSSLoaderOptions> = {

const supportModules = Boolean(options.modules || autoModules);

const modulesExports: { [filepath: string]: { [prop: string]: string } } = {};
const modulesExports: { [file: string]: { [x: string]: string } } = {};

const postcssOpts: PostCSSLoaderOptions["postcss"] & {
from: string;
Expand All @@ -99,8 +99,6 @@ const loader: Loader<PostCSSLoaderOptions> = {
const modulesOptions = typeof options.modules === "object" ? options.modules : {};
plugins.push(
...postcssModules({
// Skip hash while testing since CSS content would differ on Windows and Linux
// due to different line endings.
generateScopedName: process.env.NODE_ENV === "test" ? "[name]_[local]" : undefined,
failOnWrongOrder: true,
...modulesOptions,
Expand Down
11 changes: 5 additions & 6 deletions src/loaders/postcss/modules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@ import modulesValues from "postcss-modules-values";
import localByDefault, { LocalByDefaultOptions } from "postcss-modules-local-by-default";
import extractImports, { ExtractImportsOptions } from "postcss-modules-extract-imports";
import modulesScope, { ScopeOptions } from "postcss-modules-scope";

import generateScopedNameDefault from "./generate";

/** Options for [CSS Modules](https://github.com/css-modules/css-modules) */
export type ModulesOptions = {
export interface ModulesOptions {
/**
* Default mode for classes
* @default "local"
* */
*/
mode?: LocalByDefaultOptions["mode"];
/** Fail on wrong order of composition */
failOnWrongOrder?: ExtractImportsOptions["failOnWrongOrder"];
Expand All @@ -25,11 +24,11 @@ export type ModulesOptions = {
* - `[local]`: The original value of the selector.
* - `[hash(:<num>)]`: A hash based on the name and content of the asset (with optional length).
* @default "[name]_[local]__[hash:8]"
* */
*/
generateScopedName?: string | ScopeOptions["generateScopedName"];
/** Function for resulting replacements extraction */
getReplacements?: (file: string, replacements: { [k: string]: string }, out?: string) => void;
};
getReplacements?: (file: string, replacements: { [x: string]: string }, out?: string) => void;
}

export default (options: ModulesOptions): postcss.Transformer[] => {
const opts = {
Expand Down

0 comments on commit a460dd0

Please sign in to comment.