diff --git a/__tests__/__snapshots__/index.test.ts.snap b/__tests__/__snapshots__/index.test.ts.snap index ecef5afc..fff304b7 100644 --- a/__tests__/__snapshots__/index.test.ts.snap +++ b/__tests__/__snapshots__/index.test.ts.snap @@ -1,77 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`already-processed: js 1`] = ` -"/* eslint-env browser */ - -/** @type {HTMLElement[]} */ -const containers = []; -/** @type {{prepend:HTMLStyleElement,append:HTMLStyleElement}[]} */ -const styleTags = []; - -/** - * @param {string|undefined} css - * @param {object} [options={}] - * @param {boolean} [options.prepend] - * @param {boolean} [options.singleTag] - * @param {HTMLElement} [options.container] - * @returns {void} - */ -var injector_14187a73 = (css, options = {}) => { - if (!css || typeof document === \\"undefined\\") return; - const singleTag = typeof options.singleTag !== \\"undefined\\" ? options.singleTag : false; - const container = typeof options.container !== \\"undefined\\" ? options.container : document.head; - const position = options.prepend === true ? \\"prepend\\" : \\"append\\"; - - const createStyleTag = () => { - const styleTag = document.createElement(\\"style\\"); - styleTag.type = \\"text/css\\"; - if (position === \\"prepend\\" && container.firstChild) { - container.insertBefore(styleTag, container.firstChild); - } else { - container.append(styleTag); - } - return styleTag; - }; - - /** @type {HTMLStyleElement} */ - let styleTag; - - if (singleTag) { - let id = containers.indexOf(container); - - if (id === -1) { - id = containers.push(container) - 1; - styleTags[id] = {}; - } - - if (styleTags[id] && styleTags[id][position]) { - styleTag = styleTags[id][position]; - } else { - styleTag = styleTags[id][position] = createStyleTag(); - } - } else { - styleTag = createStyleTag(); - } - - // strip potential UTF-8 BOM if css was read from a file - if (css.charCodeAt(0) === 0xfeff) css = css.slice(1); - - if (styleTag.styleSheet) { - styleTag.styleSheet.cssText += css; - } else { - styleTag.textContent += css; - } -}; - -var css_2f84417a = \\".foo {\\\\n color: red;\\\\n}\\\\n\\"; -const stylesheet = css_2f84417a; -injector_14187a73(css_2f84417a); - -export default css_2f84417a; -export { stylesheet }; -" -`; - exports[`basic postcss-config: js 1`] = ` "'use strict'; @@ -1458,8 +1386,86 @@ console.log(style$1); " `; -exports[`multiple-instances: js 1`] = ` -"/* eslint-env browser */ +exports[`multiple-instances already-processed: js 1`] = ` +"'use strict'; + +Object.defineProperty(exports, '__esModule', { value: true }); + +/* eslint-env browser */ + +/** @type {HTMLElement[]} */ +const containers = []; +/** @type {{prepend:HTMLStyleElement,append:HTMLStyleElement}[]} */ +const styleTags = []; + +/** + * @param {string|undefined} css + * @param {object} [options={}] + * @param {boolean} [options.prepend] + * @param {boolean} [options.singleTag] + * @param {HTMLElement} [options.container] + * @returns {void} + */ +var injector_14187a73 = (css, options = {}) => { + if (!css || typeof document === \\"undefined\\") return; + const singleTag = typeof options.singleTag !== \\"undefined\\" ? options.singleTag : false; + const container = typeof options.container !== \\"undefined\\" ? options.container : document.head; + const position = options.prepend === true ? \\"prepend\\" : \\"append\\"; + + const createStyleTag = () => { + const styleTag = document.createElement(\\"style\\"); + styleTag.type = \\"text/css\\"; + if (position === \\"prepend\\" && container.firstChild) { + container.insertBefore(styleTag, container.firstChild); + } else { + container.append(styleTag); + } + return styleTag; + }; + + /** @type {HTMLStyleElement} */ + let styleTag; + + if (singleTag) { + let id = containers.indexOf(container); + + if (id === -1) { + id = containers.push(container) - 1; + styleTags[id] = {}; + } + + if (styleTags[id] && styleTags[id][position]) { + styleTag = styleTags[id][position]; + } else { + styleTag = styleTags[id][position] = createStyleTag(); + } + } else { + styleTag = createStyleTag(); + } + + // strip potential UTF-8 BOM if css was read from a file + if (css.charCodeAt(0) === 0xfeff) css = css.slice(1); + + if (styleTag.styleSheet) { + styleTag.styleSheet.cssText += css; + } else { + styleTag.textContent += css; + } +}; + +var css_2f84417a = \\".less {\\\\n color: magenta;\\\\n}\\\\n\\"; +const stylesheet = css_2f84417a; +injector_14187a73(css_2f84417a); + +exports.default = css_2f84417a; +exports.stylesheet = stylesheet; +" +`; + +exports[`multiple-instances default: js 1`] = ` +"'use strict'; + +/* eslint-env browser */ /** @type {HTMLElement[]} */ const containers = []; @@ -1527,8 +1533,8 @@ injector_14187a73(css_2f84417a); var css_2f84417a$1 = \\".less {\\\\n color: magenta;\\\\n}\\\\n\\"; injector_14187a73(css_2f84417a$1); -var css_2f84417a$2 = \\".mod_modular {\\\\n color: royalblue;\\\\n}\\\\n\\"; -var mod = {\\"modular\\":\\"mod_modular\\",\\"modularalt\\":\\"mod_modular\\"}; +var css_2f84417a$2 = \\".mod_mod {\\\\n color: royalblue;\\\\n}\\\\n\\"; +var mod = {\\"mod\\":\\"mod_mod\\"}; injector_14187a73(css_2f84417a$2); console.log(mod); diff --git a/__tests__/fixtures/multiple-instances/mod.mcss b/__tests__/fixtures/multiple-instances/mod.mcss index 6c478eb0..4c762616 100644 --- a/__tests__/fixtures/multiple-instances/mod.mcss +++ b/__tests__/fixtures/multiple-instances/mod.mcss @@ -1,3 +1,3 @@ -.modular { +.mod { color: royalblue; } diff --git a/__tests__/helpers/index.ts b/__tests__/helpers/index.ts index 47c93421..26718197 100644 --- a/__tests__/helpers/index.ts +++ b/__tests__/helpers/index.ts @@ -1,8 +1,7 @@ /* eslint-disable jest/no-export */ import path from "path"; import fs from "fs-extra"; -import { rollup } from "rollup"; -import commonjs from "@rollup/plugin-commonjs"; +import { rollup, Plugin } from "rollup"; import styles from "../../src"; import { Options } from "../../src/types"; @@ -11,13 +10,10 @@ export interface WriteData { input: string; outDir?: string; options?: Options; + plugins?: Plugin[]; } -export interface Test extends WriteData { - title: string; -} - -export interface TestData { +export interface WriteResult { js: () => Promise; css: () => Promise; map: () => Promise; @@ -29,19 +25,9 @@ export interface TestData { export const fixture = (...args: string[]): string => path.normalize(path.join(__dirname, "..", "fixtures", ...args)); -export async function write(data: WriteData): Promise { +export async function write(data: WriteData): Promise { const outDir = fixture("dist", data.outDir || ""); - const bundle = await rollup({ - input: fixture(data.input), - plugins: [commonjs(), styles(data.options)], - }); - - await bundle.write({ - format: "cjs", - file: path.join(outDir, "bundle.js"), - }); - const js = path.join(outDir, "bundle.js"); const css = data.options && typeof data.options.extract === "string" @@ -49,57 +35,62 @@ export async function write(data: WriteData): Promise { : path.join(outDir, "bundle.css"); const map = `${css}.map`; - const resData: TestData = { + const bundle = await rollup({ + input: fixture(data.input), + plugins: data.plugins || [styles(data.options)], + }); + + await bundle.write({ format: "cjs", file: js }); + + const res: WriteResult = { js: () => fs.readFile(js, "utf8"), css: () => fs.readFile(css, "utf8"), map: () => fs.readFile(map, "utf8"), isCss: () => fs.pathExists(css), isMap: () => fs.pathExists(map), - isFile: (file: string) => fs.pathExists(path.join(outDir, file)), + isFile: file => fs.pathExists(path.join(outDir, file)), }; - return resData; + return res; +} + +export interface TestData extends WriteData { + title: string; } -export function validate({ title, input, outDir, options = {} }: Test): void { - test( - title, - async () => { - let res; - try { - res = await write({ input, outDir, options }); - } catch (error) { - const frame = error.codeFrame || error.snippet; - if (frame) throw new Error(`${frame} ${error.message}`); - throw error; - } - - await expect(res.js()).resolves.toMatchSnapshot("js"); - - if (options.extract) { - await expect(res.isCss()).resolves.toBeTruthy(); - await expect(res.css()).resolves.toMatchSnapshot("css"); - } - - const sourceMap = options && options.sourceMap; - if (sourceMap === "inline") { - await expect(res.isMap()).resolves.toBeFalsy(); - } else if (sourceMap === true) { - await expect(res.isMap()).resolves.toBe(Boolean(options.extract)); - if (options.extract) await expect(res.map()).resolves.toMatchSnapshot("map"); - } - }, - 30000, - ); +export function validate(data: TestData): void { + const options = data.options || {}; + test(data.title, async () => { + let res; + try { + res = await write(data); + } catch (error) { + const frame = error.codeFrame || error.snippet; + if (frame) throw new Error(`${frame} ${error.message}`); + throw error; + } + + await expect(res.js()).resolves.toMatchSnapshot("js"); + + if (options.extract) { + await expect(res.isCss()).resolves.toBeTruthy(); + await expect(res.css()).resolves.toMatchSnapshot("css"); + } + + const sourceMap = options && options.sourceMap; + if (sourceMap === "inline") { + await expect(res.isMap()).resolves.toBeFalsy(); + } else if (sourceMap === true) { + await expect(res.isMap()).resolves.toBe(Boolean(options.extract)); + if (options.extract) await expect(res.map()).resolves.toMatchSnapshot("map"); + } + }); } -export function validateMany(groupName: string, tests: Test[]): void { +export function validateMany(groupName: string, testDatas: TestData[]): void { describe(groupName, () => { - for (const test of tests) { - validate({ - ...test, - outDir: path.join(groupName, test.title), - }); + for (const testData of testDatas) { + validate({ ...testData, outDir: path.join(groupName, testData.title) }); } }); } diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts index 523577f5..7b80e22b 100644 --- a/__tests__/index.test.ts +++ b/__tests__/index.test.ts @@ -1,5 +1,3 @@ -import path from "path"; -import fs from "fs-extra"; import { rollup } from "rollup"; import styles from "../src"; @@ -7,8 +5,6 @@ import styles from "../src"; import { fixture, validateMany, write } from "./helpers"; import { humanlizePath } from "../src/utils/path-utils"; -beforeAll(() => fs.remove(fixture("dist"))); - validateMany("basic", [ { title: "simple", @@ -258,6 +254,23 @@ validateMany("less", [ }, ]); +validateMany("multiple-instances", [ + { + title: "default", + input: "multiple-instances/index.js", + plugins: [ + styles({ extensions: [".css"], use: [] }), + styles({ extensions: [], use: ["less"] }), + styles({ extensions: [".mcss"], use: [], modules: true, namedExports: true }), + ], + }, + { + title: "already-processed", + input: "multiple-instances/bar.less", + plugins: [styles(), styles()], + }, +]); + test("on-extract-fn", async () => { const res = await write({ input: "simple/index.js", @@ -311,40 +324,3 @@ test("augment-chunk-hash", async () => { expect(barHash).not.toEqual(fooOneHash); expect(barHash).not.toEqual(fooTwoHash); }); - -test("already-processed", async () => { - const bundle = await rollup({ - input: fixture("simple/foo.css"), - plugins: [styles(), styles()], - }); - - const outDir = fixture("dist", "already-processed"); - const { output } = await bundle.write({ dir: outDir }); - const outfile = path.join(outDir, output[0].fileName); - - await expect(fs.pathExists(outfile)).resolves.toBeTruthy(); - await expect(fs.readFile(outfile, "utf8")).resolves.toMatchSnapshot("js"); -}); - -test("multiple-instances", async () => { - const bundle = await rollup({ - input: fixture("multiple-instances/index.js"), - plugins: [ - styles({ extensions: [".css"], use: [] }), - styles({ extensions: [], use: ["less"] }), - styles({ - extensions: [".mcss"], - use: [], - modules: true, - namedExports: name => `${name}alt`, - }), - ], - }); - - const outDir = fixture("dist", "multiple-instances"); - const { output } = await bundle.write({ dir: outDir }); - const outfile = path.join(outDir, output[0].fileName); - - await expect(fs.pathExists(outfile)).resolves.toBeTruthy(); - await expect(fs.readFile(outfile, "utf8")).resolves.toMatchSnapshot("js"); -}); diff --git a/__tests__/setup.ts b/__tests__/setup.ts index c8accf05..1accd37e 100644 --- a/__tests__/setup.ts +++ b/__tests__/setup.ts @@ -1,3 +1,8 @@ // eslint-disable-next-line node/no-extraneous-import import "expect-puppeteer"; + +import fs from "fs-extra"; +import { fixture } from "./helpers"; + jest.setTimeout(30000); +beforeAll(() => fs.remove(fixture("dist"))); diff --git a/src/loaders/postcss/index.ts b/src/loaders/postcss/index.ts index 8ac3b679..54dd2a2e 100644 --- a/src/loaders/postcss/index.ts +++ b/src/loaders/postcss/index.ts @@ -142,7 +142,7 @@ const loader: Loader = { .relative() .toCommentData(); - let output = ""; + let output = "\n"; if (options.namedExports) { const json = modulesExports[this.id]; @@ -158,21 +158,21 @@ const loader: Loader = { if (!json[newName]) json[newName] = json[name]; - output += `\nexport const ${newName} = ${JSON.stringify(json[name])};`; + output += `export const ${newName} = ${JSON.stringify(json[name])};\n`; } } const cssVarName = safeId("css"); if (options.extract) { - output += `\nexport default ${JSON.stringify(modulesExports[this.id])};`; + output += `export default ${JSON.stringify(modulesExports[this.id])};\n`; extracted = { id: this.id, code: res.css, map }; } else { const defaultExport = supportModules ? JSON.stringify(modulesExports[this.id]) : cssVarName; - output += `\n${[ + output += `${[ `var ${cssVarName} = ${JSON.stringify(res.css)};`, `export const stylesheet = ${cssVarName};`, `export default ${defaultExport};`, - ].join("\n")}`; + ].join("\n")}\n`; } if (!options.extract && options.inject) { @@ -190,10 +190,10 @@ const loader: Loader = { ); const injectorData = typeof options.inject === "object" ? `,${JSON.stringify(options.inject)}` : ""; - output += `\n${[ + output += `${[ `import ${injectorName} from '${injectorPath}';`, `${injectorName}(${cssVarName}${injectorData});`, - ].join("\n")}`; + ].join("\n")}\n`; } }