From df1d1ee3a9a0f2c44f47b4afaf9ad522fccb4471 Mon Sep 17 00:00:00 2001 From: Anton Kudryavtsev Date: Wed, 17 Jun 2020 15:58:41 +0300 Subject: [PATCH] perf: misc optimizations --- .../modules-duplication/postcss.config.js | 4 +- src/index.ts | 14 +++---- src/loaders/index.ts | 25 ++++++------- src/loaders/postcss/index.ts | 37 ++++--------------- src/loaders/postcss/modules/generate.ts | 11 +++--- src/loaders/postcss/url/generate.ts | 13 ++++--- src/utils/sourcemap.ts | 26 ++++++------- 7 files changed, 54 insertions(+), 76 deletions(-) diff --git a/__tests__/fixtures/modules-duplication/postcss.config.js b/__tests__/fixtures/modules-duplication/postcss.config.js index e96305f2..8c5c8da8 100644 --- a/__tests__/fixtures/modules-duplication/postcss.config.js +++ b/__tests__/fixtures/modules-duplication/postcss.config.js @@ -1,5 +1,5 @@ -module.exports = { +module.exports = () => ({ plugins: { "postcss-custom-properties": {}, }, -}; +}); diff --git a/src/index.ts b/src/index.ts index efcb2821..33af2165 100644 --- a/src/index.ts +++ b/src/index.ts @@ -123,11 +123,10 @@ export default (options: Options = {}): Plugin => { const hashable = extracted .filter(e => ids.includes(e.id)) .sort((a, b) => ids.lastIndexOf(a.id) - ids.lastIndexOf(b.id)) - .map(e => ({ - ...e, - id: path.basename(e.id), - map: mm(e.map).relative(path.dirname(e.id)).toString(), - })); + .map(e => { + const { base, dir } = path.parse(e.id); + return { ...e, id: base, map: mm(e.map).relative(dir).toString() }; + }); if (hashable.length === 0) return; @@ -137,7 +136,8 @@ export default (options: Options = {}): Plugin => { async generateBundle(opts, bundle) { if (extracted.length === 0 || !(opts.dir || opts.file)) return; - const dir = opts.dir ?? path.dirname(opts.file ?? ""); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const dir = opts.dir ?? path.dirname(opts.file!); const chunks = Object.values(bundle).filter((c): c is OutputChunk => c.type === "chunk"); const emitted = preserveModules ? chunks : chunks.filter(c => c.isEntry || c.isDynamicEntry); const emittedList: [string, string[]][] = []; @@ -180,7 +180,7 @@ export default (options: Options = {}): Plugin => { }; const getName = (chunk: OutputChunk): string => { - if (opts.file) return path.basename(opts.file, path.extname(opts.file)); + if (opts.file) return path.parse(opts.file).name; if (preserveModules) { const { dir, name } = path.parse(chunk.fileName); return dir ? `${dir}/${name}` : name; diff --git a/src/loaders/index.ts b/src/loaders/index.ts index 462b306d..b9ddde79 100644 --- a/src/loaders/index.ts +++ b/src/loaders/index.ts @@ -31,16 +31,13 @@ interface LoadersOptions { } export default class Loaders { - readonly #use: Map>; - readonly #test: (file: string) => boolean; - readonly #loaders = new Map(); + private readonly use: Map>; + private readonly test: (file: string) => boolean; + private readonly loaders = new Map(); constructor(options: LoadersOptions) { - this.#use = new Map(options.use.reverse()); - - this.#test = (file): boolean => - options.extensions.some(ext => file.toLowerCase().endsWith(ext)); - + this.use = new Map(options.use.reverse()); + this.test = (file): boolean => options.extensions.some(ext => file.toLowerCase().endsWith(ext)); this.addLoader(postcssLoader); this.addLoader(sourcemapLoader); this.addLoader(sassLoader); @@ -50,21 +47,21 @@ export default class Loaders { } addLoader>(loader: Loader): void { - if (!this.#use.has(loader.name)) return; - this.#loaders.set(loader.name, loader as Loader); + if (!this.use.has(loader.name)) return; + this.loaders.set(loader.name, loader as Loader); } isSupported(file: string): boolean { - if (this.#test(file)) return true; - for (const [, loader] of this.#loaders) { + if (this.test(file)) return true; + for (const [, loader] of this.loaders) { if (matchFile(file, loader.test)) return true; } return false; } async process(payload: Payload, context: LoaderContext): Promise { - for await (const [name, options] of this.#use) { - const loader = this.#loaders.get(name); + for await (const [name, options] of this.use) { + const loader = this.loaders.get(name); if (!loader) continue; const ctx: LoaderContext = { ...context, options }; if (loader.alwaysProcess || matchFile(ctx.id, loader.test)) { diff --git a/src/loaders/postcss/index.ts b/src/loaders/postcss/index.ts index 2a163b03..75c36c1a 100644 --- a/src/loaders/postcss/index.ts +++ b/src/loaders/postcss/index.ts @@ -2,7 +2,6 @@ import path from "path"; import { RawSourceMap } from "source-map"; import { makeLegalIdentifier } from "@rollup/pluginutils"; import postcss from "postcss"; -import postcssrc from "postcss-load-config"; import cssnano from "cssnano"; import { PostCSSLoaderOptions } from "../../types"; import { humanlizePath, normalizePath } from "../../utils/path"; @@ -10,6 +9,7 @@ import { mm } from "../../utils/sourcemap"; import resolveAsync from "../../utils/resolve-async"; import safeId from "../../utils/safe-id"; import { Loader } from "../types"; +import loadConfig from "./config"; import postcssImport from "./import"; import postcssUrl from "./url"; import postcssModules from "./modules"; @@ -17,36 +17,15 @@ import postcssICSS from "./icss"; import postcssNoop from "./noop"; let injectorId: string; +const testing = process.env.NODE_ENV === "test"; const reservedWords = ["css"]; + function getClassNameDefault(name: string): string { const id = makeLegalIdentifier(name); if (reservedWords.includes(id)) return `_${id}`; return id; } -type LoadedConfig = ReturnType extends PromiseLike ? T : never; -async function loadConfig( - id: string, - config: PostCSSLoaderOptions["config"], -): Promise> { - const configPath = - typeof config === "object" && config.path ? path.resolve(config.path) : path.dirname(id); - - const context: Record> = { - file: { - extname: path.extname(id), - dirname: path.dirname(id), - basename: path.basename(id), - }, - options: typeof config === "object" ? config.ctx ?? {} : {}, - }; - - return postcssrc(context, configPath).catch(error => { - if (!/no postcss config found/i.test((error as Error).message)) throw error; - return {}; - }); -} - function ensureAutoModules(am: PostCSSLoaderOptions["autoModules"], id: string): boolean { if (typeof am === "function") return am(id); if (am instanceof RegExp) return am.test(id); @@ -95,7 +74,7 @@ const loader: Loader = { const modulesOptions = typeof options.modules === "object" ? options.modules : {}; plugins.push( ...postcssModules({ - generateScopedName: process.env.NODE_ENV === "test" ? "[name]_[local]" : undefined, + generateScopedName: testing ? "[name]_[local]" : undefined, failOnWrongOrder: true, ...modulesOptions, }), @@ -116,12 +95,15 @@ const loader: Loader = { case "warning": this.warn({ name: msg.plugin, message: msg.text as string }); break; + case "icss": Object.assign(modulesExports, msg.export as Record); break; + case "dependency": this.deps.add(normalizePath(msg.file)); break; + case "asset": this.assets.set(msg.to, msg.source); break; @@ -176,10 +158,7 @@ const loader: Loader = { if (!injectorId) { injectorId = await resolveAsync("./inject-css", { - basedir: path.join( - process.env.NODE_ENV === "test" ? process.cwd() : __dirname, - "runtime", - ), + basedir: path.join(testing ? process.cwd() : __dirname, "runtime"), }).then(normalizePath); } diff --git a/src/loaders/postcss/modules/generate.ts b/src/loaders/postcss/modules/generate.ts index 7834fb4b..5c538c1e 100644 --- a/src/loaders/postcss/modules/generate.ts +++ b/src/loaders/postcss/modules/generate.ts @@ -7,18 +7,19 @@ import hasher from "../../../utils/hasher"; import { hashRe } from "../common"; export default (placeholder = "[name]_[local]__[hash:8]") => ( - name: string, + local: string, file: string, css: string, ): string => { - const hash = hasher(`${path.basename(file)}:${css}`); + const { dir, name, base } = path.parse(file); + const hash = hasher(`${base}:${css}`); const match = hashRe.exec(placeholder); const hashLen = match && Number.parseInt(match[1]); return makeLegalIdentifier( placeholder - .replace("[dir]", path.basename(path.dirname(file))) - .replace("[name]", path.basename(file, path.extname(file))) - .replace("[local]", name) + .replace("[dir]", path.basename(dir)) + .replace("[name]", name) + .replace("[local]", local) .replace(hashRe, hashLen ? hash.slice(0, hashLen) : hash), ); }; diff --git a/src/loaders/postcss/url/generate.ts b/src/loaders/postcss/url/generate.ts index 4fd0f084..093dc899 100644 --- a/src/loaders/postcss/url/generate.ts +++ b/src/loaders/postcss/url/generate.ts @@ -5,14 +5,15 @@ import hasher from "../../../utils/hasher"; import { hashRe } from "../common"; export default (placeholder: string, file: string, source: Uint8Array): string => { - const hash = hasher(`${path.basename(file)}:${Buffer.from(source).toString()}`); + const { dir, name, ext, base } = path.parse(file); + const hash = hasher(`${base}:${Buffer.from(source).toString()}`); const match = hashRe.exec(placeholder); const hashLen = match && Number.parseInt(match[1]); return placeholder - .replace("[dir]", path.basename(path.dirname(file))) - .replace("[name]", path.basename(file, path.extname(file))) - .replace("[extname]", path.extname(file)) - .replace(".[ext]", path.extname(file)) - .replace("[ext]", path.extname(file).slice(1)) + .replace("[dir]", path.basename(dir)) + .replace("[name]", name) + .replace("[extname]", ext) + .replace(".[ext]", ext) + .replace("[ext]", ext.slice(1)) .replace(hashRe, hashLen ? hash.slice(0, hashLen) : hash.slice(0, 8)); }; diff --git a/src/utils/sourcemap.ts b/src/utils/sourcemap.ts index 5bb6fb95..c924240f 100644 --- a/src/utils/sourcemap.ts +++ b/src/utils/sourcemap.ts @@ -25,27 +25,27 @@ export const stripMap = (code: string): string => code.replace(mapBlockRe, "").replace(mapLineRe, ""); class MapModifier { - readonly #map?: RawSourceMap; + private readonly map?: RawSourceMap; constructor(map?: string | RawSourceMap) { if (typeof map === "string") try { - this.#map = JSON.parse(map) as RawSourceMap; + this.map = JSON.parse(map) as RawSourceMap; } catch { /* noop */ } - else this.#map = map; + else this.map = map; } modify(f: (m: RawSourceMap) => void): this { - if (!this.#map) return this; - f(this.#map); + if (!this.map) return this; + f(this.map); return this; } modifySources(op: (source: string) => string): this { - if (!this.#map) return this; - if (this.#map.sources) this.#map.sources = this.#map.sources.map(s => op(s)); + if (!this.map) return this; + if (this.map.sources) this.map.sources = this.map.sources.map(s => op(s)); return this; } @@ -65,17 +65,17 @@ class MapModifier { } toObject(): RawSourceMap | undefined { - return this.#map; + return this.map; } toString(): string | undefined { - if (!this.#map) return this.#map; - return JSON.stringify(this.#map); + if (!this.map) return this.map; + return JSON.stringify(this.map); } async toConsumer(): Promise { - if (!this.#map) return this.#map; - return new SourceMapConsumer(this.#map); + if (!this.map) return this.map; + return new SourceMapConsumer(this.map); } toCommentData(): string { @@ -86,7 +86,7 @@ class MapModifier { } toCommentFile(fileName: string): string { - if (!this.#map) return ""; + if (!this.map) return ""; return `\n/*# sourceMappingURL=${fileName} */`; } }