From b6713426dd939fa92f38673cc5ada23359ddd5db Mon Sep 17 00:00:00 2001 From: Marco Pasqualetti Date: Wed, 25 Sep 2024 18:49:18 +0200 Subject: [PATCH] feat: output insertStyle as part of rollup bundle --- README.md | 7 -- package.json | 2 +- src/index.ts | 48 ++++++--- src/insertStyle.ts | 7 +- test/index.test.ts | 93 +++++++++++++++--- test/snapshots/test/index.test.ts.md | 11 +-- test/snapshots/test/index.test.ts.snap | Bin 885 -> 863 bytes tsconfig.build.insertStyle.json | 8 -- ...g.build.plugin.json => tsconfig.build.json | 3 +- 9 files changed, 123 insertions(+), 56 deletions(-) delete mode 100644 tsconfig.build.insertStyle.json rename tsconfig.build.plugin.json => tsconfig.build.json (58%) diff --git a/README.md b/README.md index ee0883f..09de321 100644 --- a/README.md +++ b/README.md @@ -94,13 +94,6 @@ sass({ There is a utility function that handles injecting individual style payloads into the page's head, which is output as `___$insertStyle` by the rollup-plugin-sass plugin. -This function is output to `./dist/node_modules/...`, in user-land builds, so you have to make sure that it isn't -ignored by your build tool(s) (E.g., rollup, webpack etc.); As a solution, you'll just have to make sure that the -directory is "included"/not-"excluded" via your build tools facilities/added-plugins/etc. - -Additionally, if you're publishing an app to an internal registry, or similar, you'll have to -make sure 'dist/node_modules' isn't ignored in this scenario as well. - ### `processor` - Type: `Function` diff --git a/package.json b/package.json index f36a88e..11bedab 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "scripts": { "prepare": "npm run build && npm test && husky", - "build": "npm run build-downlevel-dts && tsc --project tsconfig.build.plugin.json && tsc --project tsconfig.build.insertStyle.json", + "build": "npm run build-downlevel-dts && tsc --project tsconfig.build.json", "build-downlevel-dts": "node scripts/clean-and-run-downlevel-dts.js", "downlevel-dts": "downlevel-dts . ts3.5 [--to=3.5]", "test": "nyc --reporter=html --reporter=text ava && npm run test:rollup.config.spec.ts", diff --git a/src/index.ts b/src/index.ts index 14aae03..989989a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,8 +6,6 @@ import * as fs from "fs"; import { createFilter } from "@rollup/pluginutils"; import type { SassImporterResult, - RollupAssetInfo, - RollupChunkInfo, RollupPluginSassOptions, RollupPluginSassOutputFn, SassOptions, @@ -15,6 +13,7 @@ import type { SassRenderResult, } from "./types"; import { isFunction, isObject, isString, warn } from "./utils"; +import insertStyle from "./insertStyle"; // @note Rollup is added as a "devDependency" so no actual symbols should be imported. // Interfaces and non-concrete types are ok. @@ -26,13 +25,18 @@ type PluginState = { // ""; Used, currently to ensure that we're not pushing style objects representing // the same file-path into `pluginState.styles` more than once. - styleMaps: { [index: string]: { id?: string; content?: string } }; + styleMaps: { + [index: string]: { + id?: string; + content?: string; + }; + }; }; const MATCH_SASS_FILENAME_RE = /\.sass$/; const MATCH_NODE_MODULE_RE = /^~([a-z0-9]|@).+/i; -const insertFnName = "___$insertStyle"; +const INSERT_STYLE_ID = "___$insertStyle"; /** * Returns a sass `importer` list: @@ -147,12 +151,11 @@ const processRenderResponse = ( if (rollupOptions.insert) { /** - * Add `insertStyle` import for handling "inserting" - * *.css into *.html `head`. - * @see insertStyle.ts for additional information + * Include import using {@link INSERT_STYLE_ID} as source. + * It will be resolved to insert style function using `resolvedID` and `load` hooks */ - imports = `import ${insertFnName} from '${__dirname}/insertStyle.js';\n`; - defaultExport = `${insertFnName}(${out});`; + imports = `import ${INSERT_STYLE_ID} from '${INSERT_STYLE_ID}';\n`; + defaultExport = `${INSERT_STYLE_ID}(${out});`; } else if (!rollupOptions.output) { defaultExport = out; } @@ -178,13 +181,16 @@ export = function plugin( }, options, ); + const { include = defaultIncludes, exclude = defaultExcludes, runtime: sassRuntime, options: incomingSassOptions = {} as SassOptions, } = pluginOptions; + const filter = createFilter(include || "", exclude || ""); + const pluginState: PluginState = { styles: [], styleMaps: {}, @@ -193,7 +199,21 @@ export = function plugin( return { name: "rollup-plugin-sass", - transform(code: string, filePath: string): Promise { + /** @see https://rollupjs.org/plugin-development/#resolveid */ + resolveId(source) { + if (source === INSERT_STYLE_ID) { + return INSERT_STYLE_ID; + } + }, + + /** @see https://rollupjs.org/plugin-development/#load */ + load(id) { + if (id === INSERT_STYLE_ID) { + return `export default ${insertStyle.toString()}`; + } + }, + + transform(code, filePath) { if (!filter(filePath)) { return Promise.resolve(); } @@ -245,11 +265,7 @@ export = function plugin( ); // @note do not `catch` here - let error propagate to rollup level. }, - generateBundle( - generateOptions: { file?: string }, - bundle: { [fileName: string]: RollupAssetInfo | RollupChunkInfo }, - isWrite: boolean, - ): Promise { + generateBundle(generateOptions, _, isWrite) { if ( !isWrite || (!pluginOptions.insert && @@ -284,5 +300,5 @@ export = function plugin( return Promise.resolve(css); }, - } as RollupPlugin; + }; }; diff --git a/src/insertStyle.ts b/src/insertStyle.ts index b339db6..2ac555c 100644 --- a/src/insertStyle.ts +++ b/src/insertStyle.ts @@ -1,10 +1,9 @@ /** * Create a style tag and append to head tag * - * @warning this file is not included directly in the source code! - * If user specifies inject option to true, an import to this file will be injected in rollup output. - * Due to this reason this file is compiled into a ESM module separated from other plugin source files. - * That is the reason of why there are two tsconfig.build files. + * @warning This function is injected inside rollup. According to this be sure + * - to not include any side-effect + * - do not import any library / other files content * * @return css style */ diff --git a/test/index.test.ts b/test/index.test.ts index f7888af..a19215b 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -163,9 +163,46 @@ test("should support options.data", async (t) => { onwarn, }); const { output } = await outputBundle.generate(TEST_GENERATE_OPTIONS); - const result = getFirstChunkCode(output); - t.snapshot(result); + debugger; + + const outputFilePath = path.join(TEST_OUTPUT_DIR, "insert-bundle"); + + await outputBundle.write({ dir: outputFilePath }); + + t.is( + output.length, + 1, + "has 1 chunk (we are bundling all in one single file)", + ); + + const [{ moduleIds, modules }] = output; + + t.is( + moduleIds.filter((it) => it.endsWith("insertStyle")).length, + 1, + "include insertStyle one time", + ); + + const actualAModuleID = moduleIds.find((it) => + it.endsWith("actual_a.scss"), + ) as string; + const actualAModule = modules[actualAModuleID]; + t.truthy(actualAModule); + t.snapshot( + actualAModule.code, + "actual_a content is compiled with insertStyle", + ); + + const actualBModuleID = moduleIds.find((it) => + it.endsWith("actual_b.scss"), + ) as string; + const actualBModule = modules[actualBModuleID]; + t.truthy(actualBModule); + t.snapshot( + actualBModule.code, + "actual_b content is compiled with insertStyle", + ); }); test("should generate chunks with import insertStyle when `insert` is true", async (t) => { @@ -180,26 +217,58 @@ test("should support options.data", async (t) => { options: TEST_SASS_OPTIONS_DEFAULT, }), ], - output: { - preserveModules: true, - preserveModulesRoot: "src", - }, + onwarn, }); - const { output } = await outputBundle.generate(TEST_GENERATE_OPTIONS); + const { output } = await outputBundle.generate({ + ...TEST_GENERATE_OPTIONS, + preserveModules: true, + preserveModulesRoot: "src", + }); + + const outputFilePath = path.join(TEST_OUTPUT_DIR, "insert-multiple-entry"); + + await outputBundle.write({ + dir: outputFilePath, + preserveModules: true, + preserveModulesRoot: "src", + }); + + t.is(output.length, 5, "has 5 chunks"); + + const outputFileNames = output.map((it) => it.fileName); + + t.is( + outputFileNames.filter((it) => it.startsWith("entry")).length, + 2, + "1 chunk for each entry (2)", + ); + t.is( + outputFileNames.filter((it) => it.startsWith("assets/actual")).length, + 2, + "1 chunk for each entry style import (2)", + ); + t.is( + outputFileNames.filter((it) => it.endsWith("insertStyle.js")).length, + 1, + "1 chunk for insertStyle helper", + ); + + const styleFiles = output.filter((it) => + it.fileName.startsWith("assets/actual"), + ); - t.is(output.length, 2, "has 2 chunks"); t.true( - output.every((outputItem) => { + styleFiles.every((outputItem) => { if (outputItem.type === "chunk") { const insertStyleImportsCount = outputItem.imports.filter((it) => - it.includes("/insertStyle.js"), + it.endsWith("insertStyle.js"), ).length; return insertStyleImportsCount === 1; } - // if is an assets there is no need to check imports - return true; + // no asset should be present here + return false; }), "each chunk must include insertStyle once", ); diff --git a/test/snapshots/test/index.test.ts.md b/test/snapshots/test/index.test.ts.md index 6a4cfab..40a82fc 100644 --- a/test/snapshots/test/index.test.ts.md +++ b/test/snapshots/test/index.test.ts.md @@ -65,12 +65,11 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 - `import ___$insertStyle from '../../../src/insertStyle.js';␊ - ␊ - ___$insertStyle("body{color:red}");␊ - ␊ - ___$insertStyle("body{color:green}");␊ - ` + 'insertStyle("body{color:red}");' + +> Snapshot 2 + + 'insertStyle("body{color:green}");' ## should processor return as string diff --git a/test/snapshots/test/index.test.ts.snap b/test/snapshots/test/index.test.ts.snap index ad1931905ef6366fe6a97c32eace4442613ae38f..dd8855835b1d99da9ec5d287d9f61ea574201cb1 100644 GIT binary patch literal 863 zcmV-l1EBmtRzVNk>UX!yE7jya6wO_WqLdonwa}MZ(qD{$}U3^Md6hpiQ2ty!m`|H`l1v0q$ueT_t5+6V&4K&{cpd(k}Y z#oSIvn%jiW@(ogT81J;S3Zz&YZ;FWWsL;=_SZPX02DB%*E=Kqg#_S?|MHN|dvY+~k#f&kTsKQ>P|Zk_bd>akCV+?S&Xe pA;CP@ z9jF_PzaNVT00000000BsRy}VMK@fF72+18C5(tI}WI}=?QBcGrBA65bA&}gp2srEA zv3>E~?rCSwaby{UD5%oXCnX&v9S#40pTIxh51_rTyYIc3*`337 z%T>Hb2S;ce?s?F7=rPW&-A}!z#?Tl-M6~{?z!c#~ zfdg1gc3VtVjepc@xEcr)=B8!^6Pr+7gwIIkqAr|K_D5ZHyU{_ z+9$o3+X_i@yW!J(fz(=z_c~eyQrsUeij-RtJ%wFBMtN~kQNy0ny@K+6o?*9Y*^$|I zaYFH?BqC5Tg=;Yd3j7eQd|M?{wHxLcyv#6IKlW#18ixc{%;IKJ){;<%xdBHS1zltU z4C~~Xe9Bbq$5Px7&1Um6R)egRH>EAV?lRwLKdcg;>0f; z3%V5x^0nefq%>$WftFT38STgdNe3Bo-(~x7Bv7wt&R+_bg zPE(>km#nVAIPq%6SN}0UAa2&uAGpz|=%O-9>v_Zl-F;`k9bgOcpq4X}HEx zyx~$Vyx41qz1B#|?IXOkIH?3SJtcy|amCFP&70HC`<%9-L1d;2VQm`EaN1CT@dE?9 ztj~yR$2X8u@!{wns6dys&>dXmUF5xlrACV8Ac6moQ)NR^pwW(F2BiTUIAp+)u+eyt zFtyWLOtGJ#*sTPFVe4^IiOcp}jH7f_F)jIUwdrK*&l(xBan; diff --git a/tsconfig.build.insertStyle.json b/tsconfig.build.insertStyle.json deleted file mode 100644 index 94f6d05..0000000 --- a/tsconfig.build.insertStyle.json +++ /dev/null @@ -1,8 +0,0 @@ -/* @see insertStyle.ts for additional information */ -{ - "extends": "./tsconfig.json", - "include": ["./src/insertStyle.ts"], - "compilerOptions": { - "module": "ES6" - } -} diff --git a/tsconfig.build.plugin.json b/tsconfig.build.json similarity index 58% rename from tsconfig.build.plugin.json rename to tsconfig.build.json index e218d24..83caea9 100644 --- a/tsconfig.build.plugin.json +++ b/tsconfig.build.json @@ -1,6 +1,5 @@ /* @see insertStyle.ts for additional information */ { "extends": "./tsconfig.json", - "include": ["./src/*"], - "exclude": ["./src/insertStyle.ts"] + "include": ["./src/*"] }