From 9005ab8c4124f18cefcf94064abe191e60d1cca0 Mon Sep 17 00:00:00 2001 From: Anton Kudryavtsev Date: Fri, 8 May 2020 19:17:12 +0300 Subject: [PATCH] feat(import): resolve extensions --- __tests__/fixtures/resolvers/features/foo.css | 2 +- __tests__/fixtures/resolvers/style.scss | 2 +- __tests__/helpers/index.ts | 7 +++ __tests__/utils.test.ts | 37 +++++++-------- pnpm-lock.yaml | 46 ++++++++----------- src/loaders/postcss/icss/index.ts | 16 +++---- src/loaders/postcss/icss/resolve.ts | 4 +- src/loaders/postcss/import/index.ts | 26 ++++++----- src/loaders/postcss/import/resolve.ts | 9 ++-- src/loaders/postcss/index.ts | 2 +- src/loaders/postcss/url/index.ts | 9 ++-- src/loaders/postcss/url/resolve.ts | 3 ++ src/utils/sourcemap.ts | 2 +- 13 files changed, 87 insertions(+), 78 deletions(-) diff --git a/__tests__/fixtures/resolvers/features/foo.css b/__tests__/fixtures/resolvers/features/foo.css index 0133e606..15637b1c 100644 --- a/__tests__/fixtures/resolvers/features/foo.css +++ b/__tests__/fixtures/resolvers/features/foo.css @@ -1,4 +1,4 @@ -@import "sub/subfoo.css"; +@import "sub/subfoo"; .foo { background-image: url("./bg.png"), url("./bg.testing.regex.png"), url("@/bg.testing.regex.png"); diff --git a/__tests__/fixtures/resolvers/style.scss b/__tests__/fixtures/resolvers/style.scss index 5425ba35..f33b5c68 100644 --- a/__tests__/fixtures/resolvers/style.scss +++ b/__tests__/fixtures/resolvers/style.scss @@ -1,4 +1,4 @@ -@import url(features/foo.css); +@import "features/foo.css"; @import "features2/bar"; .ignore-data-uri { diff --git a/__tests__/helpers/index.ts b/__tests__/helpers/index.ts index f2f19672..6d2ede30 100644 --- a/__tests__/helpers/index.ts +++ b/__tests__/helpers/index.ts @@ -33,6 +33,13 @@ export async function write(data: WriteData): Promise { const bundle = await rollup({ input: fixture(data.input), plugins: data.plugins ?? [styles(data.options)], + onwarn: (warning, warn) => { + if (warning.code === "EMPTY_BUNDLE") return; + if (warning.source === "lit-element") return; + if (/Exported `\S+` as `\S+` in \S+/.test(warning.message)) return; + if (/Skipping processed file \S+/.test(warning.message)) return; + warn(warning); + }, }); const { output } = await bundle.write({ diff --git a/__tests__/utils.test.ts b/__tests__/utils.test.ts index ab693a3b..e32486bd 100644 --- a/__tests__/utils.test.ts +++ b/__tests__/utils.test.ts @@ -4,18 +4,15 @@ import { mm, getMap, stripMap } from "../src/utils/sourcemap"; describe("load-module", () => { test("wrong path", async () => { - const wrong = await loadModule("totallyWRONGPATH/here"); - expect(wrong).toBeUndefined(); + await expect(loadModule("totallyWRONGPATH/here")).resolves.toBeUndefined(); }); test("correct cwd path", async () => { - const correct = await loadModule(fixture("utils/fixture")); - expect(correct).toBe("this is fixture"); + await expect(loadModule(fixture("utils/fixture"))).resolves.toBe("this is fixture"); }); test("correct path with custom basepath", async () => { - const correct = await loadModule("fixture", fixture("utils")); - expect(correct).toBe("this is fixture"); + await expect(loadModule("fixture", fixture("utils"))).resolves.toBe("this is fixture"); }); }); @@ -23,22 +20,27 @@ describe("sourcemap-utils", () => { test("inline map", async () => { let code = '.foo {color: red;background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAG1BMVEXMzMyWlpacnJy+vr6jo6PFxcW3t7eqqqqxsbHbm8QuAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAiklEQVRYhe3QMQ6EIBAF0C+GSInF9mYTs+1ewRsQbmBlayysKefYO2asXbbYxvxHQj6ECQMAEREREf2NQ/fCtp5Zky6vtRMkSJEzhyISynWJnzH6Z8oQlzS7lEc/fLmmQUSvc16OrCPqRl1JePxQYo1ZSWVj9nxrrOb5esw+eXdvzTWfTERERHRXH4tWFZGswQ2yAAAAAElFTkSuQmCC");}'; - const noMap = await getMap(code); - expect(noMap).toBeUndefined(); + + await expect(getMap(code)).resolves.toBeUndefined(); + code += "/*# sourceMappingURL=data:application/json;base64,e1RISVM6SVNBU09VUkNFTUFQU0lNVUxBVElPTn0= */"; - const correctMap = await getMap(code); - expect(correctMap).toBe("{THIS:ISASOURCEMAPSIMULATION}"); + + await expect(getMap(code)).resolves.toBe("{THIS:ISASOURCEMAPSIMULATION}"); }); test("file map", async () => { const code = ".foo {color: red;}/*# sourceMappingURL=fixture.css.map */"; - const noPathMap = await getMap(code); - expect(noPathMap).toBeUndefined(); - const wrongPathMap = await getMap(code, "this/is/nonexistant/path.css"); - expect(wrongPathMap).toBeUndefined(); - const correctMap = await getMap(code, fixture("utils/pointless.css")); - expect(correctMap).toBe("{THIS:ISASOURCEMAPSIMULATION}"); + + await expect(getMap(code)).rejects.toMatchObject( + new Error("Extracted map detected, but no ID is provided"), + ); + + await expect(getMap(code, "this/is/nonexistant/path.css")).resolves.toBeUndefined(); + + await expect(getMap(code, fixture("utils/pointless.css"))).resolves.toBe( + "{THIS:ISASOURCEMAPSIMULATION}", + ); }); test("strip map", () => { @@ -50,7 +52,6 @@ describe("sourcemap-utils", () => { const map = JSON.stringify({ sources: ["../a/b/../foo/bar.css", "../b/a/../bar/foo.css"] }); const relativeSrc = JSON.stringify(mm(map).relative().toObject()?.sources); expect(relativeSrc).toBe(JSON.stringify(["../a/foo/bar.css", "../b/bar/foo.css"])); - const wrongMap = mm("thisisnotjson").toString(); - expect(wrongMap).toBeUndefined(); + expect(mm("thisisnotjson").toString()).toBeUndefined(); }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3dca5c58..ca65e00c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1367,7 +1367,7 @@ packages: /@octokit/core/2.5.0: dependencies: '@octokit/auth-token': 2.4.0 - '@octokit/graphql': 4.3.1 + '@octokit/graphql': 4.4.0 '@octokit/request': 5.4.2 '@octokit/types': 2.14.0 before-after-hook: 2.1.0 @@ -1383,14 +1383,14 @@ packages: dev: true resolution: integrity: sha512-pOPHaSz57SFT/m3R5P8MUu4wLPszokn5pXcB/pzavLTQf2jbU+6iayTvzaY6/BiotuRS0qyEUkx3QglT4U958A== - /@octokit/graphql/4.3.1: + /@octokit/graphql/4.4.0: dependencies: '@octokit/request': 5.4.2 '@octokit/types': 2.14.0 - universal-user-agent: 4.0.1 + universal-user-agent: 5.0.0 dev: true resolution: - integrity: sha512-hCdTjfvrK+ilU2keAdqNBWOk+gm1kai1ZcdjRfB30oA3/T6n53UVJb7w0L5cR3/rhU91xT3HSqCd+qbvH06yxA== + integrity: sha512-Du3hAaSROQ8EatmYoSAJjzAz3t79t9Opj/WY1zUgxVUGfIKn0AEjg+hlOLscF6fv6i/4y/CeUvsWgIfwMkTccw== /@octokit/plugin-paginate-rest/2.2.0: dependencies: '@octokit/types': 2.14.0 @@ -1576,7 +1576,7 @@ packages: aggregate-error: 3.0.1 debug: 4.1.1 dir-glob: 3.0.1 - execa: 4.0.0 + execa: 4.0.1 lodash: 4.17.15 micromatch: 4.0.2 p-reduce: 2.1.0 @@ -1618,7 +1618,7 @@ packages: dependencies: '@semantic-release/error': 2.2.0 aggregate-error: 3.0.1 - execa: 4.0.0 + execa: 4.0.1 fs-extra: 9.0.0 lodash: 4.17.15 nerf-dart: 1.0.0 @@ -2236,7 +2236,7 @@ packages: /autoprefixer/9.7.6: dependencies: browserslist: 4.12.0 - caniuse-lite: 1.0.30001053 + caniuse-lite: 1.0.30001054 chalk: 2.4.2 normalize-range: 0.1.2 num2fraction: 1.2.2 @@ -2439,8 +2439,8 @@ packages: integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== /browserslist/4.12.0: dependencies: - caniuse-lite: 1.0.30001053 - electron-to-chromium: 1.3.430 + caniuse-lite: 1.0.30001054 + electron-to-chromium: 1.3.431 node-releases: 1.1.55 pkg-up: 2.0.0 hasBin: true @@ -2557,15 +2557,15 @@ packages: /caniuse-api/3.0.0: dependencies: browserslist: 4.12.0 - caniuse-lite: 1.0.30001053 + caniuse-lite: 1.0.30001054 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 dev: false resolution: integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== - /caniuse-lite/1.0.30001053: + /caniuse-lite/1.0.30001054: resolution: - integrity: sha512-HtV4wwIZl6GA4Oznse8aR274XUOYGZnQLcf/P8vHgmlfqSNelwD+id8CyHOceqLqt9yfKmo7DUZTh1EuS9pukg== + integrity: sha512-jiKlTI6Ur8Kjfj8z0muGrV6FscpRvefcQVPSuMuXnvRCfExU7zlVLNjmOz1TnurWgUrAY7MMmjyy+uTgIl1XHw== /capture-exit/2.0.0: dependencies: rsvp: 4.8.5 @@ -3457,9 +3457,9 @@ packages: dev: true resolution: integrity: sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= - /electron-to-chromium/1.3.430: + /electron-to-chromium/1.3.431: resolution: - integrity: sha512-HMDYkANGhx6vfbqpOf/hc6hWEmiOipOHGDeRDeUb3HLD3XIWpvKQxFgWf0tgHcr3aNv6I/8VPecplqmQsXoZSw== + integrity: sha512-2okqkXCIda7qDwjYhUFxPcQdZDIZZ/zBLDzVOif7WW/TSNfEhdT6SO07O1x/sFteEHX189Z//UwjbZKKCOn2Fg== /elegant-spinner/2.0.0: dev: true engines: @@ -3498,7 +3498,7 @@ packages: integrity: sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== /env-ci/5.0.2: dependencies: - execa: 4.0.0 + execa: 4.0.1 java-properties: 1.0.2 dev: true engines: @@ -3872,7 +3872,7 @@ packages: node: '>=6' resolution: integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== - /execa/4.0.0: + /execa/4.0.1: dependencies: cross-spawn: 7.0.2 get-stream: 5.1.0 @@ -3887,7 +3887,7 @@ packages: engines: node: '>=10' resolution: - integrity: sha512-JbDUxwV3BoT5ZVXQrSVbAiaXhXUkIwvbhPIwZ0N13kX+5yCzOhUNdocxB/UQRuYOHRYYwAxKYwJYc0T4D12pDA== + integrity: sha512-SCjM/zlBdOK8Q5TIjOn6iEHZaPHFsMoTxXQ2nvUvtPnuohz3H2dIozSg+etNR98dGoYUp2ENSKLL/XaMmbxVgw== /exit-hook/1.1.1: dev: true engines: @@ -5301,7 +5301,7 @@ packages: /jest-changed-files/26.0.1: dependencies: '@jest/types': 26.0.1 - execa: 4.0.0 + execa: 4.0.1 throat: 5.0.0 dev: true engines: @@ -5938,7 +5938,7 @@ packages: cosmiconfig: 6.0.0 debug: 4.1.1 dedent: 0.7.0 - execa: 4.0.0 + execa: 4.0.1 listr2: 1.3.8 log-symbols: 3.0.0 micromatch: 4.0.2 @@ -8371,7 +8371,7 @@ packages: cosmiconfig: 6.0.0 debug: 4.1.1 env-ci: 5.0.2 - execa: 4.0.0 + execa: 4.0.1 figures: 3.2.0 find-versions: 3.2.0 get-stream: 5.1.0 @@ -9498,12 +9498,6 @@ packages: node: '>=8' resolution: integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== - /universal-user-agent/4.0.1: - dependencies: - os-name: 3.1.0 - dev: true - resolution: - integrity: sha512-LnST3ebHwVL2aNe4mejI9IQh2HfZ1RLo8Io2HugSif8ekzD1TlWpHpColOB/eh8JHMLkGH3Akqf040I+4ylNxg== /universal-user-agent/5.0.0: dependencies: os-name: 3.1.0 diff --git a/src/loaders/postcss/icss/index.ts b/src/loaders/postcss/icss/index.ts index f5fb0c51..6a3208cb 100644 --- a/src/loaders/postcss/icss/index.ts +++ b/src/loaders/postcss/icss/index.ts @@ -14,19 +14,15 @@ export type InteroperableCSSOptions = { const plugin: postcss.Plugin = postcss.plugin( name, - options => async (css, res): Promise => { + (options = {}) => async (css, res): Promise => { if (!css.source?.input.file) return; if (!res.processor) return; - const load = options?.load ?? loadDefault; - const extensions = options?.extensions ?? [".css"]; + const load = options.load ?? loadDefault; + const extensions = options.extensions ?? [".css", ".pcss", ".postcss", ".sss"]; - const opts = res.opts - ? { - ...res.opts, - map: typeof res.opts.map === "object" ? { ...res.opts.map, prev: false } : res.opts.map, - } - : {}; + const opts = res.opts && { ...res.opts }; + delete opts?.map; const { icssExports, icssImports } = extractICSS(css); @@ -48,7 +44,7 @@ const plugin: postcss.Plugin = postcss.plugin( res.messages.push({ plugin: name, type: "icss", replacements }); - if (typeof options?.getReplacements === "function") + if (typeof options.getReplacements === "function") options.getReplacements(css.source.input.file, replacements, res.opts?.to); }, ); diff --git a/src/loaders/postcss/icss/resolve.ts b/src/loaders/postcss/icss/resolve.ts index c6fddeed..ea26f3ba 100644 --- a/src/loaders/postcss/icss/resolve.ts +++ b/src/loaders/postcss/icss/resolve.ts @@ -11,12 +11,12 @@ export default async function ( processor: postcss.Processor, opts?: postcss.ProcessOptions, ): Promise { - return Object.entries(icssImports).reduce(async (result, [url, values]) => { + return Object.entries(icssImports).reduce(async (acc, [url, values]) => { const exports = await load(url, file, extensions, processor, opts); const mappedValues = Object.entries(values).reduce((acc, [k, v]) => { acc[k] = exports[v]; return acc; }, {}); - return { ...(await result), ...mappedValues }; + return { ...(await acc), ...mappedValues }; }, Promise.resolve({})); } diff --git a/src/loaders/postcss/import/index.ts b/src/loaders/postcss/import/index.ts index ff363179..32435727 100644 --- a/src/loaders/postcss/import/index.ts +++ b/src/loaders/postcss/import/index.ts @@ -8,6 +8,7 @@ import resolveDefault, { Resolve } from "./resolve"; const name = "styles-import"; +/** `@import` handler options */ export type ImportOptions = { /** * Provide custom resolver for imports @@ -22,20 +23,18 @@ export type ImportOptions = { alias?: { [from: string]: string }; }; -const plugin: postcss.Plugin = postcss.plugin( +type ImportPrivateOptions = { extensions?: string[] }; +const plugin: postcss.Plugin = postcss.plugin( name, - options => async (css, res): Promise => { + (options = {}) => async (css, res): Promise => { if (!css.source?.input.file) return; - const resolve = options?.resolve ?? resolveDefault; - const alias = options?.alias ?? {}; + const resolve = options.resolve ?? resolveDefault; + const alias = options.alias ?? {}; + const extensions = options.extensions ?? [".css", ".pcss", ".postcss", ".sss"]; - const opts = res.opts - ? { - ...res.opts, - map: typeof res.opts.map === "object" ? { ...res.opts.map, prev: false } : res.opts.map, - } - : {}; + const opts = res.opts && { ...res.opts }; + delete opts?.map; const { file } = css.source.input; const importMap = new Map(); @@ -96,9 +95,12 @@ const plugin: postcss.Plugin = postcss.plugin( for await (const [importRule, url] of importMap) { try { - const { source, from } = await resolve(url, basedir); + const { source, from } = await resolve(url, basedir, extensions); - if (!(source instanceof Uint8Array) || typeof from !== "string") continue; + if (!(source instanceof Uint8Array) || typeof from !== "string") { + importRule.warn(res, `Incorrectly resolved \`@import\` in \`${importRule.toString()}\``); + continue; + } if (normalizePath(from) === normalizePath(file)) { importRule.warn(res, `\`@import\` loop in \`${importRule.toString()}\``); diff --git a/src/loaders/postcss/import/resolve.ts b/src/loaders/postcss/import/resolve.ts index fadf949c..494e00b9 100644 --- a/src/loaders/postcss/import/resolve.ts +++ b/src/loaders/postcss/import/resolve.ts @@ -2,11 +2,14 @@ import fs from "fs-extra"; import resolveAsync from "../../../utils/resolve-async"; +/** File resolved by `@import` resolver */ export type ResolvedFile = { from: string; source: Uint8Array }; -export type Resolve = (url: string, basedir: string) => Promise; -const resolve: Resolve = async (url, basedir) => { - const options = { basedir }; +/** `@import` resolver */ +export type Resolve = (url: string, basedir: string, extensions: string[]) => Promise; + +const resolve: Resolve = async (url, basedir, extensions) => { + const options = { basedir, extensions }; let from: string; try { from = await resolveAsync(url, options); diff --git a/src/loaders/postcss/index.ts b/src/loaders/postcss/index.ts index e35c5189..164e475a 100644 --- a/src/loaders/postcss/index.ts +++ b/src/loaders/postcss/index.ts @@ -57,7 +57,7 @@ const loader: Loader = { const plugins = [ ...[ - options.import && postcssImport(options.import), + options.import && postcssImport({ ...options.import, extensions: options.extensions }), options.url && postcssUrl(options.url), ].filter(booleanFilter), ...(options.postcss.plugins ?? []), diff --git a/src/loaders/postcss/url/index.ts b/src/loaders/postcss/url/index.ts index 3fd4f995..035e173f 100644 --- a/src/loaders/postcss/url/index.ts +++ b/src/loaders/postcss/url/index.ts @@ -14,6 +14,7 @@ import inlineFile from "./inline"; const name = "styles-url"; +/** URL handler options */ export type UrlOptions = { /** * Inline files instead of copying @@ -57,9 +58,8 @@ export type UrlOptions = { const plugin: postcss.Plugin = postcss.plugin( name, - options => async (css, res): Promise => { + (options = {}) => async (css, res): Promise => { if (!css.source?.input.file) return; - if (!options) return; const inline = options.inline ?? false; const publicPath = options.publicPath ?? "./"; @@ -150,7 +150,10 @@ const plugin: postcss.Plugin = postcss.plugin( try { const { source, from } = await resolve(url, basedir); - if (!(source instanceof Uint8Array) || typeof from !== "string") continue; + if (!(source instanceof Uint8Array) || typeof from !== "string") { + decl.warn(res, `Incorrectly resolved URL \`${url}\` in \`${decl.toString()}\``); + continue; + } res.messages.push({ plugin: name, type: "dependency", file: from }); diff --git a/src/loaders/postcss/url/resolve.ts b/src/loaders/postcss/url/resolve.ts index fadf949c..86fe9e6c 100644 --- a/src/loaders/postcss/url/resolve.ts +++ b/src/loaders/postcss/url/resolve.ts @@ -2,7 +2,10 @@ import fs from "fs-extra"; import resolveAsync from "../../../utils/resolve-async"; +/** File resolved by URL resolver */ export type ResolvedFile = { from: string; source: Uint8Array }; + +/** URL resolver */ export type Resolve = (url: string, basedir: string) => Promise; const resolve: Resolve = async (url, basedir) => { diff --git a/src/utils/sourcemap.ts b/src/utils/sourcemap.ts index a2200f79..801e5fe4 100644 --- a/src/utils/sourcemap.ts +++ b/src/utils/sourcemap.ts @@ -18,7 +18,7 @@ export async function getMap(code: string, id?: string): Promise