@lynx-js/css-extract-webpack-plugin 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @lynx-js/css-extract-webpack-plugin
2
2
 
3
+ ## 0.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - **BREAKING CHANGE** ([#2803](https://github.com/lynx-family/lynx-stack/pull/2803))
8
+
9
+ Remove `CssExtractWebpackPlugin` / `CssExtractWebpackPluginOptions` along with the `mini-css-extract-plugin` dependency. Use `CssExtractRspackPlugin` instead.
10
+
11
+ The `cssPlugins` option is now optional, defaulting to `[CSS.Plugins.removeFunctionWhiteSpace()]`.
12
+
13
+ - **BREAKING CHANGE** ([#2803](https://github.com/lynx-family/lynx-stack/pull/2803))
14
+
15
+ Drop webpack support — the plugins now target Rspack only. All public types come from `@rspack/core` instead of `webpack` (e.g. `Compiler`, `Compilation`, `LoaderContext`), and the `webpack` dependency is removed.
16
+
17
+ ### Patch Changes
18
+
19
+ - Prefix Lynx runtime module names with `webpack/runtime/` (e.g. `Lynx async chunks` → `webpack/runtime/lynx async chunks`), matching the path-structured naming of the bundler's built-in runtime modules. The previous bare names had no path segment, so when they appear as a source-map `sources` entry under a `file://` module-filename template they collapsed into an invalid URL authority (the space-containing name became the host) and broke `SourceMapConsumer` parsing. ([#2642](https://github.com/lynx-family/lynx-stack/pull/2642))
20
+
21
+ - Widen peer ranges to admit the new minor versions of `@lynx-js/template-webpack-plugin` (^0.12.0) and `@lynx-js/rspeedy` (^0.15.0) shipping with the unified `debug-metadata.json` feature. ([#2642](https://github.com/lynx-family/lynx-stack/pull/2642))
22
+
3
23
  ## 0.7.1
4
24
 
5
25
  ### Patch Changes
@@ -9,7 +9,7 @@ interface CssExtractRspackPluginOptions extends ExternalCssExtractRspackPluginOp
9
9
  /**
10
10
  * plugins passed to parser
11
11
  */
12
- cssPlugins: Parameters<typeof LynxTemplatePlugin.convertCSSChunksToMap>[1];
12
+ cssPlugins?: Parameters<typeof LynxTemplatePlugin.convertCSSChunksToMap>[1];
13
13
  /**
14
14
  * The name of non-initial CSS chunk files
15
15
  */
@@ -75,7 +75,7 @@ declare class CssExtractRspackPlugin {
75
75
  *
76
76
  * @public
77
77
  */
78
- static defaultOptions: Readonly<CssExtractRspackPluginOptions>;
78
+ static defaultOptions: Readonly<CssExtractRspackPluginOptions & Pick<Required<CssExtractRspackPluginOptions>, 'cssPlugins'>>;
79
79
  /**
80
80
  * The entry point of a webpack plugin.
81
81
  * @param compiler - the webpack compiler
@@ -107,9 +107,7 @@ class CssExtractRspackPluginImpl {
107
107
  }).apply(compiler);
108
108
  compiler.hooks.thisCompilation.tap(this.name, (compilation) => {
109
109
  if (this.isHMREnabled(compiler)) {
110
- const hooks = LynxTemplatePlugin.getLynxTemplatePluginHooks(
111
- // @ts-expect-error Rspack to Webpack Compilation
112
- compilation);
110
+ const hooks = LynxTemplatePlugin.getLynxTemplatePluginHooks(compilation);
113
111
  hooks.beforeEmit.tapPromise(this.name, async (args) => {
114
112
  const cssChunks = args.cssChunks;
115
113
  const content = cssChunks.map((chunk) => chunk.source.source().toString('utf-8'));
@@ -119,7 +117,8 @@ class CssExtractRspackPluginImpl {
119
117
  if (!hotUpdateFilePath) {
120
118
  continue;
121
119
  }
122
- const css = LynxTemplatePlugin.convertCSSChunksToMap(content, options.cssPlugins, Boolean(args.finalEncodeOptions.compilerOptions['enableCSSSelector']));
120
+ const css = LynxTemplatePlugin.convertCSSChunksToMap(content, options.cssPlugins
121
+ ?? CssExtractRspackPlugin.defaultOptions.cssPlugins, Boolean(args.finalEncodeOptions.compilerOptions['enableCSSSelector']));
123
122
  const cssDeps = Object.entries(css.cssMap).reduce((acc, [key, value]) => {
124
123
  const importRuleNodes = value.filter((node) => node.type === 'ImportRule');
125
124
  acc[key] = importRuleNodes.map(({ href }) => href);
@@ -129,10 +128,12 @@ class CssExtractRspackPluginImpl {
129
128
  const { compilerOptions: {
130
129
  // remove the `templateDebugUrl` to avoid "emit different content to the same filename" error while chunk splitting is enabled, see #1481
131
130
  templateDebugUrl, ...restCompilerOptions }, } = args.finalEncodeOptions;
131
+ const baseEncodeOptions = { ...args.finalEncodeOptions };
132
+ baseEncodeOptions.compilerOptions = restCompilerOptions;
133
+ delete baseEncodeOptions.elementTemplate;
132
134
  const { buffer } = await hooks.encode.promise({
133
135
  encodeOptions: {
134
- ...args.finalEncodeOptions,
135
- compilerOptions: restCompilerOptions,
136
+ ...baseEncodeOptions,
136
137
  css,
137
138
  lepusCode: {
138
139
  root: undefined,
@@ -167,7 +168,7 @@ class CssExtractRspackPluginImpl {
167
168
  const { RuntimeGlobals, RuntimeModule } = compiler.webpack;
168
169
  class CSSHotUpdateRuntimeModule extends RuntimeModule {
169
170
  constructor(hash, hotUpdateFiles) {
170
- super('lynx css hot update');
171
+ super('webpack/runtime/lynx css hot update');
171
172
  this.hash = hash;
172
173
  this.hotUpdateFiles = hotUpdateFiles;
173
174
  }
package/lib/index.d.ts CHANGED
@@ -3,9 +3,7 @@
3
3
  *
4
4
  * This plugin extracts CSS into separate files. It creates a CSS file per JS file which contains CSS and CSSId.
5
5
  */
6
- export { CssExtractWebpackPlugin } from './CssExtractWebpackPlugin.js';
7
- export type { CssExtractWebpackPluginOptions } from './CssExtractWebpackPlugin.js';
8
- export type { LoaderOptions } from './loader.js';
9
- export type { LoaderOptions as CssExtractRspackLoaderOptions } from './loader.js';
6
+ export type { LoaderOptions } from './rspack-loader.js';
7
+ export type { LoaderOptions as CssExtractRspackLoaderOptions } from './rspack-loader.js';
10
8
  export { CssExtractRspackPlugin } from './CssExtractRspackPlugin.js';
11
9
  export type { CssExtractRspackPluginOptions } from './CssExtractRspackPlugin.js';
package/lib/index.js CHANGED
@@ -1,11 +1,5 @@
1
1
  // Copyright 2024 The Lynx Authors. All rights reserved.
2
2
  // Licensed under the Apache License Version 2.0 that can be found in the
3
3
  // LICENSE file in the root directory of this source tree.
4
- /**
5
- * @packageDocumentation
6
- *
7
- * This plugin extracts CSS into separate files. It creates a CSS file per JS file which contains CSS and CSSId.
8
- */
9
- export { CssExtractWebpackPlugin } from './CssExtractWebpackPlugin.js';
10
4
  export { CssExtractRspackPlugin } from './CssExtractRspackPlugin.js';
11
5
  //# sourceMappingURL=index.js.map
@@ -1,5 +1,54 @@
1
1
  import type { LoaderContext } from '@rspack/core';
2
- import type { LoaderOptions } from './loader.js';
3
2
  export declare function pitch(this: LoaderContext<LoaderOptions>, request: string,
4
3
  /** previousRequest */ _: string, data: Record<string, unknown>): Promise<void>;
5
4
  export default function loader(this: LoaderContext<LoaderOptions>, content: string): string | undefined;
5
+ /**
6
+ * The options of CSS extract loader.
7
+ *
8
+ * @public
9
+ */
10
+ export interface LoaderOptions {
11
+ /**
12
+ * The same as {@link https://github.com/webpack-contrib/mini-css-extract-plugin/tree/master?tab=readme-ov-file#emit | mini-css-extract-plugin}.
13
+ * Control whether emit the CSS to filesystem.
14
+ *
15
+ * - If `true`(default), emits a file (writes a file to the filesystem).
16
+ *
17
+ * - If `false`, the plugin will extract the CSS but will not emit the file.
18
+ *
19
+ * It is often useful to disable this option for server-side packages.
20
+ *
21
+ * @defaultValue true
22
+ * @public
23
+ */
24
+ emit?: boolean;
25
+ /**
26
+ * {@inheritdoc @lynx-js/rspeedy#CssExtractRspackLoaderOptions.esModule}
27
+ */
28
+ esModule?: boolean | undefined;
29
+ /**
30
+ * The layer of the CSS execution.
31
+ *
32
+ * @remarks
33
+ *
34
+ * This should be combined with `experiments.layers`.
35
+ */
36
+ layer?: string | undefined;
37
+ }
38
+ interface DependencySourceMap {
39
+ mappings?: string | undefined;
40
+ [key: string]: unknown;
41
+ }
42
+ export interface Dep {
43
+ identifier: string;
44
+ context: string | null;
45
+ content: Buffer;
46
+ media: string;
47
+ identifierIndex?: number;
48
+ supports?: string | undefined;
49
+ layer?: string | undefined;
50
+ sourceMap?: Buffer | undefined;
51
+ }
52
+ export declare function load(this: LoaderContext<LoaderOptions>, request: string, addDependencies: (deps: Dep[]) => void): Promise<string>;
53
+ export declare function offsetSourceMapLines<T extends DependencySourceMap>(sourceMap: T, lineOffset: number): T;
54
+ export {};
@@ -1,7 +1,9 @@
1
1
  // Copyright 2024 The Lynx Authors. All rights reserved.
2
2
  // Licensed under the Apache License Version 2.0 that can be found in the
3
3
  // LICENSE file in the root directory of this source tree.
4
- import { load } from './loader.js';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { extractPathFromIdentifier, stringifyRequest } from './util.js';
5
7
  export async function pitch(request,
6
8
  /** previousRequest */ _, data) {
7
9
  if (this._compiler?.options?.experiments?.css) {
@@ -17,9 +19,7 @@ export async function pitch(request,
17
19
  // So the `load` function may crash.
18
20
  // We make an temporary try-catch here.
19
21
  // See: https://github.com/web-infra-dev/rspack/issues/8536
20
- const resultSource = await load.call(
21
- // @ts-expect-error webpack & rspack loaderContext
22
- this, request, addDependencies.bind(this));
22
+ const resultSource = await load.call(this, request, addDependencies.bind(this));
23
23
  callback(null, resultSource, undefined, data);
24
24
  }
25
25
  catch (error) {
@@ -47,4 +47,195 @@ export default function loader(content) {
47
47
  }
48
48
  return;
49
49
  }
50
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
51
+ const BASE_URI = 'webpack://';
52
+ export async function load(request, addDependencies) {
53
+ /** TODO: schema */
54
+ const options = this.getOptions();
55
+ const emit = options.emit ?? true;
56
+ const esModule = options.esModule ?? true;
57
+ const moduleExports = await new Promise((resolve, reject) => {
58
+ this.importModule(`${this.resourcePath}.webpack[javascript/auto]!=!!!${request}`, {
59
+ baseUri: `${BASE_URI}/`,
60
+ layer: options.layer,
61
+ }, (err, exports) => {
62
+ if (err) {
63
+ return reject(err);
64
+ }
65
+ return resolve(exports);
66
+ });
67
+ });
68
+ let locals;
69
+ if (isNamedExports(moduleExports)) {
70
+ Object.keys(moduleExports).forEach((key) => {
71
+ if (key !== 'default') {
72
+ locals ??= {};
73
+ locals[key] = moduleExports[key];
74
+ }
75
+ });
76
+ }
77
+ else {
78
+ locals =
79
+ (isCJSExports(moduleExports) ? moduleExports : moduleExports.default)
80
+ ?.locals;
81
+ }
82
+ let dependencies;
83
+ const exportContent = isCJSExports(moduleExports)
84
+ ? moduleExports
85
+ : moduleExports.default;
86
+ const { cssId: rawCssId } = parseQuery(this.resourceQuery);
87
+ const cssId = rawCssId ?? '';
88
+ const identifierCountMap = new Map();
89
+ if (Array.isArray(exportContent)) {
90
+ dependencies = exportContent.map(([identifier, content, media, sourceMap, supports, layer]) => {
91
+ const count = identifierCountMap.get(identifier) ?? 0;
92
+ const rawResourcePath = extractPathFromIdentifier(identifier, true);
93
+ const [resourcePath, resourceQuery] = rawResourcePath.split('?');
94
+ const params = new URLSearchParams(resourceQuery ? `?${resourceQuery}` : '');
95
+ if (params.get('cssId') === null) {
96
+ params.set('cssId', cssId);
97
+ }
98
+ const filePath = path.relative(this.rootContext, extractPathFromIdentifier(identifier));
99
+ const shouldWrapCSSId = Boolean(cssId)
100
+ && (params.get('common') === null
101
+ || params.get('common') === 'false');
102
+ identifierCountMap.set(identifier, count + 1);
103
+ return {
104
+ identifier: identifier.replace(rawResourcePath, `${resourcePath}?${params.toString()}`),
105
+ context: this.rootContext,
106
+ content: Buffer.from(shouldWrapCSSId
107
+ /**
108
+ * Given the following source code:
109
+ *
110
+ * ```css foo.css?cssId=1001
111
+ * @import 'bar.css'
112
+ * .foo {
113
+ * color: red;
114
+ * }
115
+ * ```
116
+ *
117
+ * ```css bar.css
118
+ * .bar {
119
+ * color: blue;
120
+ * }
121
+ * ```
122
+ *
123
+ * The output should be:
124
+ *
125
+ * ```css
126
+ * @cssId "1001" "bar.css" {
127
+ * .bar {
128
+ * color: blue;
129
+ * }
130
+ * }
131
+ * @cssId "1001" "foo.css" {
132
+ * .foo {
133
+ * color: red;
134
+ * }
135
+ * }
136
+ * ```
137
+ */
138
+ ? `@cssId "${cssId}" "${filePath}" {
139
+ ${content}
140
+ }
141
+ `
142
+ : content),
143
+ media,
144
+ supports,
145
+ layer,
146
+ identifierIndex: count,
147
+ sourceMap: sourceMap
148
+ ? Buffer.from(JSON.stringify(shouldWrapCSSId ? offsetSourceMapLines(sourceMap, 1) : sourceMap))
149
+ : undefined,
150
+ };
151
+ });
152
+ addDependencies(dependencies);
153
+ }
154
+ else {
155
+ dependencies = [[null, exportContent]];
156
+ }
157
+ const result = (function makeResult() {
158
+ if (locals) {
159
+ if (isNamedExports(moduleExports)) {
160
+ const identifiers = Array.from((function* generateIdentifiers() {
161
+ let identifierId = 0;
162
+ for (const key of Object.keys(locals)) {
163
+ identifierId += 1;
164
+ yield [`_${identifierId.toString(16)}`, key];
165
+ }
166
+ })());
167
+ const localsString = identifiers
168
+ .map(
169
+ // TODO: support function locals
170
+ ([id, key]) => `\nvar ${id} = ${JSON.stringify(locals[key])};`)
171
+ .join('');
172
+ const exportsString = `export { ${identifiers
173
+ .map(([id, key]) => `${id} as ${JSON.stringify(key)}`)
174
+ .join(', ')} }`;
175
+ return `${localsString}\n${exportsString}\n`;
176
+ }
177
+ return `\n${esModule ? 'export default' : 'module.exports = '} ${JSON.stringify(locals)};`;
178
+ }
179
+ else if (esModule) {
180
+ return '\nexport {};';
181
+ }
182
+ return '';
183
+ })();
184
+ let resultSource = `// extracted by mini-css-extract-plugin`;
185
+ // only attempt hot reloading if the css is actually used for something other than hash values
186
+ resultSource += this.hot && emit
187
+ ? hotLoader(result, {
188
+ loaderContext: this,
189
+ options,
190
+ locals,
191
+ cssId,
192
+ })
193
+ : result;
194
+ return resultSource;
195
+ }
196
+ export function offsetSourceMapLines(sourceMap, lineOffset) {
197
+ if (lineOffset <= 0 || !sourceMap.mappings) {
198
+ return sourceMap;
199
+ }
200
+ return {
201
+ ...sourceMap,
202
+ mappings: `${';'.repeat(lineOffset)}${sourceMap.mappings}`,
203
+ };
204
+ }
205
+ function hotLoader(content, context) {
206
+ const localsJsonString = JSON.stringify(JSON.stringify(context.locals));
207
+ return `${content}
208
+ if (module.hot) {
209
+ (function() {
210
+ var localsJsonString = ${localsJsonString};
211
+ // ${Date.now()}
212
+ var cssReload = require(${stringifyRequest(context.loaderContext, path.resolve(__dirname, '../runtime/hotModuleReplacement.cjs'))})(module.id, ${JSON.stringify(context.options)}, "${context.cssId ?? '0'}");
213
+ // only invalidate when locals change
214
+ if (
215
+ module.hot.data &&
216
+ module.hot.data.value &&
217
+ module.hot.data.value !== localsJsonString
218
+ ) {
219
+ module.hot.invalidate();
220
+ } else {
221
+ module.hot.accept();
222
+ }
223
+ module.hot.dispose(function(data) {
224
+ data.value = localsJsonString;
225
+ cssReload();
226
+ });
227
+ })();
228
+ }`;
229
+ }
230
+ function isCJSExports(exports) {
231
+ return !exports.__esModule;
232
+ }
233
+ function isNamedExports(exports) {
234
+ return !isCJSExports(exports)
235
+ && (!exports.default || !('locals' in exports.default));
236
+ }
237
+ function parseQuery(query) {
238
+ const params = new URLSearchParams(query);
239
+ return Object.fromEntries(params);
240
+ }
50
241
  //# sourceMappingURL=rspack-loader.js.map
package/lib/util.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { LoaderOptions } from 'mini-css-extract-plugin';
2
- import type { LoaderContext } from 'webpack';
1
+ import type { LoaderContext } from '@rspack/core';
2
+ import type { LoaderOptions } from './rspack-loader.js';
3
3
  export declare function isAbsolutePath(str: string): boolean;
4
4
  export declare function isRelativePath(str: string): boolean;
5
5
  export declare function stringifyRequest(loaderContext: LoaderContext<LoaderOptions>, request: string): string;
package/lib/util.js CHANGED
@@ -12,7 +12,7 @@ export function isRelativePath(str) {
12
12
  export function stringifyRequest(loaderContext, request) {
13
13
  if (typeof loaderContext.utils !== 'undefined'
14
14
  && typeof loaderContext.utils.contextify === 'function') {
15
- return JSON.stringify(loaderContext.utils.contextify(loaderContext.context || loaderContext.rootContext, request));
15
+ return JSON.stringify(loaderContext.utils.contextify(loaderContext.context ?? loaderContext.rootContext, request));
16
16
  }
17
17
  const split = request.split('!');
18
18
  const { context } = loaderContext;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lynx-js/css-extract-webpack-plugin",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "This plugin extracts CSS into separate files. It creates a CSS file per JS file which contains CSS.",
5
5
  "keywords": [
6
6
  "webpack",
@@ -40,28 +40,24 @@
40
40
  "CHANGELOG.md",
41
41
  "README.md"
42
42
  ],
43
- "dependencies": {
44
- "mini-css-extract-plugin": "^2.10.0"
45
- },
46
43
  "devDependencies": {
47
44
  "@microsoft/api-extractor": "7.58.2",
48
- "@rspack/core": "1.7.9",
45
+ "@rspack/core": "2.0.8",
46
+ "@rstest/core": "0.10.5",
49
47
  "css-loader": "^7.1.4",
50
48
  "sass-loader": "^16.0.7",
51
- "webpack": "^5.105.2",
52
49
  "@lynx-js/css-serializer": "0.1.6",
53
- "@lynx-js/template-webpack-plugin": "0.11.0",
54
- "@lynx-js/test-tools": "0.0.0",
55
- "@lynx-js/vitest-setup": "0.0.0"
50
+ "@lynx-js/template-webpack-plugin": "0.12.0",
51
+ "@lynx-js/test-tools": "0.0.0"
56
52
  },
57
53
  "peerDependencies": {
58
- "@lynx-js/template-webpack-plugin": "^0.11.0"
54
+ "@lynx-js/template-webpack-plugin": "^0.11.0 || ^0.12.0"
59
55
  },
60
56
  "engines": {
61
57
  "node": ">=18"
62
58
  },
63
59
  "scripts": {
64
60
  "api-extractor": "api-extractor run --verbose",
65
- "test": "pnpm -w run test --project webpack/css-extract"
61
+ "test": "rstest"
66
62
  }
67
63
  }
@@ -1,74 +0,0 @@
1
- import MiniCssExtractPlugin from 'mini-css-extract-plugin';
2
- import type { Compiler } from 'webpack';
3
- /**
4
- * The options for CssExtractWebpackPlugin
5
- *
6
- * @public
7
- */
8
- interface CssExtractWebpackPluginOptions extends MiniCssExtractPlugin.PluginOptions {
9
- }
10
- /**
11
- * @public
12
- *
13
- * CssExtractWebpackPlugin is the CSS extract plugin for Lynx.
14
- * It works just like the {@link https://github.com/webpack-contrib/mini-css-extract-plugin | MiniCssExtractPlugin} in Web.
15
- *
16
- * @example
17
- * ```js
18
- * import { CssExtractWebpackPlugin } from '@lynx-js/css-extract-webpack-plugin'
19
- * export default {
20
- * plugins: [new CssExtractWebpackPlugin()],
21
- * module: {
22
- * rules: [
23
- * {
24
- * test: /\.css$/,
25
- * uses: [CssExtractWebpackPlugin.loader, 'css-loader'],
26
- * },
27
- * ],
28
- * },
29
- * }
30
- * ```
31
- */
32
- declare class CssExtractWebpackPlugin {
33
- private readonly options?;
34
- constructor(options?: CssExtractWebpackPluginOptions | undefined);
35
- /**
36
- * The loader to extract CSS.
37
- *
38
- * @remarks
39
- * It should be used with the {@link https://github.com/webpack-contrib/css-loader | 'css-loader'}.
40
- *
41
- * @example
42
- *
43
- * ```js
44
- * import { CssExtractWebpackPlugin } from '@lynx-js/css-extract-webpack-plugin'
45
- * export default {
46
- * plugins: [new CssExtractWebpackPlugin()],
47
- * module: {
48
- * rules: [
49
- * {
50
- * test: /\.css$/,
51
- * uses: [CssExtractWebpackPlugin.loader, 'css-loader'],
52
- * },
53
- * ],
54
- * },
55
- * }
56
- * ```
57
- *
58
- * @public
59
- */
60
- static loader: string;
61
- /**
62
- * `defaultOptions` is the default options that the {@link CssExtractWebpackPlugin} uses.
63
- *
64
- * @public
65
- */
66
- static defaultOptions: Readonly<Required<CssExtractWebpackPluginOptions>>;
67
- /**
68
- * The entry point of a webpack plugin.
69
- * @param compiler - the webpack compiler
70
- */
71
- apply(compiler: Compiler): void;
72
- }
73
- export { CssExtractWebpackPlugin };
74
- export type { CssExtractWebpackPluginOptions };
@@ -1,99 +0,0 @@
1
- // Copyright 2024 The Lynx Authors. All rights reserved.
2
- // Licensed under the Apache License Version 2.0 that can be found in the
3
- // LICENSE file in the root directory of this source tree.
4
- import { createRequire } from 'node:module';
5
- import MiniCssExtractPlugin from 'mini-css-extract-plugin';
6
- const require = createRequire(import.meta.url);
7
- /**
8
- * @public
9
- *
10
- * CssExtractWebpackPlugin is the CSS extract plugin for Lynx.
11
- * It works just like the {@link https://github.com/webpack-contrib/mini-css-extract-plugin | MiniCssExtractPlugin} in Web.
12
- *
13
- * @example
14
- * ```js
15
- * import { CssExtractWebpackPlugin } from '@lynx-js/css-extract-webpack-plugin'
16
- * export default {
17
- * plugins: [new CssExtractWebpackPlugin()],
18
- * module: {
19
- * rules: [
20
- * {
21
- * test: /\.css$/,
22
- * uses: [CssExtractWebpackPlugin.loader, 'css-loader'],
23
- * },
24
- * ],
25
- * },
26
- * }
27
- * ```
28
- */
29
- class CssExtractWebpackPlugin {
30
- constructor(options) {
31
- this.options = options;
32
- }
33
- /**
34
- * The loader to extract CSS.
35
- *
36
- * @remarks
37
- * It should be used with the {@link https://github.com/webpack-contrib/css-loader | 'css-loader'}.
38
- *
39
- * @example
40
- *
41
- * ```js
42
- * import { CssExtractWebpackPlugin } from '@lynx-js/css-extract-webpack-plugin'
43
- * export default {
44
- * plugins: [new CssExtractWebpackPlugin()],
45
- * module: {
46
- * rules: [
47
- * {
48
- * test: /\.css$/,
49
- * uses: [CssExtractWebpackPlugin.loader, 'css-loader'],
50
- * },
51
- * ],
52
- * },
53
- * }
54
- * ```
55
- *
56
- * @public
57
- */
58
- static { this.loader = require.resolve('./loader.js'); }
59
- /**
60
- * `defaultOptions` is the default options that the {@link CssExtractWebpackPlugin} uses.
61
- *
62
- * @public
63
- */
64
- static { this.defaultOptions = Object.freeze({
65
- filename: '[name].css',
66
- chunkFilename: undefined,
67
- ignoreOrder: undefined,
68
- insert: undefined,
69
- attributes: undefined,
70
- linkType: undefined,
71
- runtime: undefined,
72
- experimentalUseImportModule: undefined,
73
- }); }
74
- /**
75
- * The entry point of a webpack plugin.
76
- * @param compiler - the webpack compiler
77
- */
78
- apply(compiler) {
79
- new CssExtractWebpackPluginImpl(compiler, Object.assign({}, CssExtractWebpackPlugin.defaultOptions, this.options));
80
- }
81
- }
82
- export { CssExtractWebpackPlugin };
83
- class CssExtractWebpackPluginImpl {
84
- constructor(compiler, options) {
85
- this.options = options;
86
- this.name = 'CssExtractWebpackPlugin';
87
- new MiniCssExtractPlugin({
88
- filename: options.filename,
89
- chunkFilename: options.chunkFilename,
90
- ignoreOrder: options.ignoreOrder,
91
- insert: options.insert,
92
- attributes: options.attributes,
93
- linkType: options.linkType,
94
- runtime: options.runtime,
95
- experimentalUseImportModule: options.experimentalUseImportModule,
96
- }).apply(compiler);
97
- }
98
- }
99
- //# sourceMappingURL=CssExtractWebpackPlugin.js.map
package/lib/loader.d.ts DELETED
@@ -1,53 +0,0 @@
1
- import type { LoaderContext } from 'webpack';
2
- /**
3
- * The options of CSS extract loader.
4
- *
5
- * @public
6
- */
7
- export interface LoaderOptions {
8
- /**
9
- * The same as {@link https://github.com/webpack-contrib/mini-css-extract-plugin/tree/master?tab=readme-ov-file#emit | mini-css-extract-plugin}.
10
- * Control whether emit the CSS to filesystem.
11
- *
12
- * - If `true`(default), emits a file (writes a file to the filesystem).
13
- *
14
- * - If `false`, the plugin will extract the CSS but will not emit the file.
15
- *
16
- * It is often useful to disable this option for server-side packages.
17
- *
18
- * @defaultValue true
19
- * @public
20
- */
21
- emit?: boolean;
22
- /**
23
- * {@inheritdoc @lynx-js/rspeedy#CssExtractRspackLoaderOptions.esModule}
24
- */
25
- esModule?: boolean | undefined;
26
- /**
27
- * The layer of the CSS execution.
28
- *
29
- * @remarks
30
- *
31
- * This should be combined with `experiments.layers`.
32
- */
33
- layer?: string | undefined;
34
- }
35
- interface DependencySourceMap {
36
- mappings?: string | undefined;
37
- [key: string]: unknown;
38
- }
39
- export interface Dep {
40
- identifier: string;
41
- context: string | null;
42
- content: Buffer;
43
- media: string;
44
- identifierIndex?: number;
45
- supports?: string | undefined;
46
- layer?: string | undefined;
47
- sourceMap?: Buffer | undefined;
48
- }
49
- export declare function load(this: LoaderContext<LoaderOptions>, request: string, addDependencies: (deps: Dep[]) => void): Promise<string>;
50
- export declare function offsetSourceMapLines<T extends DependencySourceMap>(sourceMap: T, lineOffset: number): T;
51
- export declare function pitch(this: LoaderContext<LoaderOptions>, request: string): Promise<string | undefined>;
52
- export default function loader(this: LoaderContext<LoaderOptions>, content: string): string | undefined;
53
- export {};
package/lib/loader.js DELETED
@@ -1,241 +0,0 @@
1
- // Copyright 2024 The Lynx Authors. All rights reserved.
2
- // Licensed under the Apache License Version 2.0 that can be found in the
3
- // LICENSE file in the root directory of this source tree.
4
- import path from 'node:path';
5
- import { fileURLToPath } from 'node:url';
6
- import MiniCssExtractPlugin from 'mini-css-extract-plugin';
7
- import { extractPathFromIdentifier, stringifyRequest } from './util.js';
8
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
- const BASE_URI = 'webpack://';
10
- export async function load(request, addDependencies) {
11
- /** TODO: schema */
12
- const options = this.getOptions();
13
- const emit = options.emit ?? true;
14
- const esModule = options.esModule ?? true;
15
- const moduleExports = await new Promise((resolve, reject) => {
16
- this.importModule(`${this.resourcePath}.webpack[javascript/auto]!=!!!${request}`, {
17
- baseUri: `${BASE_URI}/`,
18
- layer: options.layer,
19
- }, (err, exports) => {
20
- if (err) {
21
- return reject(err);
22
- }
23
- return resolve(exports);
24
- });
25
- });
26
- let locals;
27
- if (isNamedExports(moduleExports)) {
28
- Object.keys(moduleExports).forEach((key) => {
29
- if (key !== 'default') {
30
- locals ??= {};
31
- locals[key] = moduleExports[key];
32
- }
33
- });
34
- }
35
- else {
36
- locals =
37
- (isCJSExports(moduleExports) ? moduleExports : moduleExports.default)
38
- ?.locals;
39
- }
40
- let dependencies;
41
- const exportContent = isCJSExports(moduleExports)
42
- ? moduleExports
43
- : moduleExports.default;
44
- const { cssId: rawCssId } = parseQuery(this.resourceQuery);
45
- const cssId = rawCssId ?? '';
46
- const identifierCountMap = new Map();
47
- if (Array.isArray(exportContent)) {
48
- dependencies = exportContent.map(([identifier, content, media, sourceMap, supports, layer]) => {
49
- const count = identifierCountMap.get(identifier) ?? 0;
50
- const rawResourcePath = extractPathFromIdentifier(identifier, true);
51
- const [resourcePath, resourceQuery] = rawResourcePath.split('?');
52
- const params = new URLSearchParams(resourceQuery ? `?${resourceQuery}` : '');
53
- if (params.get('cssId') === null) {
54
- params.set('cssId', cssId);
55
- }
56
- const filePath = path.relative(this.rootContext, extractPathFromIdentifier(identifier));
57
- const shouldWrapCSSId = Boolean(cssId)
58
- && (params.get('common') === null
59
- || params.get('common') === 'false');
60
- identifierCountMap.set(identifier, count + 1);
61
- return {
62
- identifier: identifier.replace(rawResourcePath, `${resourcePath}?${params.toString()}`),
63
- context: this.rootContext,
64
- content: Buffer.from(shouldWrapCSSId
65
- /**
66
- * Given the following source code:
67
- *
68
- * ```css foo.css?cssId=1001
69
- * @import 'bar.css'
70
- * .foo {
71
- * color: red;
72
- * }
73
- * ```
74
- *
75
- * ```css bar.css
76
- * .bar {
77
- * color: blue;
78
- * }
79
- * ```
80
- *
81
- * The output should be:
82
- *
83
- * ```css
84
- * @cssId "1001" "bar.css" {
85
- * .bar {
86
- * color: blue;
87
- * }
88
- * }
89
- * @cssId "1001" "foo.css" {
90
- * .foo {
91
- * color: red;
92
- * }
93
- * }
94
- * ```
95
- */
96
- ? `@cssId "${cssId}" "${filePath}" {
97
- ${content}
98
- }
99
- `
100
- : content),
101
- media,
102
- supports,
103
- layer,
104
- identifierIndex: count,
105
- sourceMap: sourceMap
106
- ? Buffer.from(JSON.stringify(shouldWrapCSSId ? offsetSourceMapLines(sourceMap, 1) : sourceMap))
107
- : undefined,
108
- };
109
- });
110
- addDependencies(dependencies);
111
- }
112
- else {
113
- dependencies = [[null, exportContent]];
114
- }
115
- const result = (function makeResult() {
116
- if (locals) {
117
- if (isNamedExports(moduleExports)) {
118
- const identifiers = Array.from((function* generateIdentifiers() {
119
- let identifierId = 0;
120
- for (const key of Object.keys(locals)) {
121
- identifierId += 1;
122
- yield [`_${identifierId.toString(16)}`, key];
123
- }
124
- })());
125
- const localsString = identifiers
126
- .map(
127
- // TODO: support function locals
128
- ([id, key]) => `\nvar ${id} = ${JSON.stringify(locals[key])};`)
129
- .join('');
130
- const exportsString = `export { ${identifiers
131
- .map(([id, key]) => `${id} as ${JSON.stringify(key)}`)
132
- .join(', ')} }`;
133
- return `${localsString}\n${exportsString}\n`;
134
- }
135
- return `\n${esModule ? 'export default' : 'module.exports = '} ${JSON.stringify(locals)};`;
136
- }
137
- else if (esModule) {
138
- return '\nexport {};';
139
- }
140
- return '';
141
- })();
142
- let resultSource = `// extracted by ${MiniCssExtractPlugin.pluginName}`;
143
- // only attempt hot reloading if the css is actually used for something other than hash values
144
- resultSource += this.hot && emit
145
- ? hotLoader(result, {
146
- loaderContext: this,
147
- options,
148
- locals,
149
- cssId,
150
- })
151
- : result;
152
- return resultSource;
153
- }
154
- export function offsetSourceMapLines(sourceMap, lineOffset) {
155
- if (lineOffset <= 0 || !sourceMap.mappings) {
156
- return sourceMap;
157
- }
158
- return {
159
- ...sourceMap,
160
- mappings: `${';'.repeat(lineOffset)}${sourceMap.mappings}`,
161
- };
162
- }
163
- export async function pitch(request) {
164
- if (this._compiler?.options?.experiments?.css
165
- && this._module
166
- && (this._module.type === 'css'
167
- || this._module.type === 'css/auto'
168
- || this._module.type === 'css/global'
169
- || this._module.type === 'css/module')) {
170
- this.emitWarning(new Error('You can\'t use `experiments.css` (`experiments.futureDefaults` enable built-in CSS support by default) and `@lynx-js/css-extract-webpack-plugin` together, please set `experiments.css` to `false` or set `{ type: "javascript/auto" }` for rules with `@lynx-js/css-extract-webpack-plugin` in your webpack config (now `@lynx-js/css-extract-webpack-plugin` does nothing).'));
171
- return;
172
- }
173
- /** TODO: schema */
174
- const options = this.getOptions();
175
- const emit = options.emit ?? true;
176
- const addDependencies = (dependencies) => {
177
- const { webpack } = this._compiler;
178
- if (!Array.isArray(dependencies) && dependencies !== null) {
179
- throw new Error(`Exported value was not extracted as an array: ${JSON.stringify(dependencies)}`);
180
- }
181
- const identifierCountMap = new Map();
182
- for (const dependency of dependencies) {
183
- if (!('identifier' in dependency) || !emit) {
184
- continue;
185
- }
186
- const CssDependency = MiniCssExtractPlugin.getCssDependency(webpack);
187
- const count = identifierCountMap.get(dependency.identifier) ?? 0;
188
- this._module.addDependency(new CssDependency(dependency, dependency.context, count));
189
- identifierCountMap.set(dependency.identifier, count + 1);
190
- }
191
- };
192
- return await load.call(this, request, addDependencies);
193
- }
194
- export default function loader(content) {
195
- if (this._compiler?.options?.experiments?.css
196
- && this._module
197
- && (this._module.type === 'css'
198
- || this._module.type === 'css/auto'
199
- || this._module.type === 'css/global'
200
- || this._module.type === 'css/module')) {
201
- return content;
202
- }
203
- return;
204
- }
205
- function hotLoader(content, context) {
206
- const localsJsonString = JSON.stringify(JSON.stringify(context.locals));
207
- return `${content}
208
- if (module.hot) {
209
- (function() {
210
- var localsJsonString = ${localsJsonString};
211
- // ${Date.now()}
212
- var cssReload = require(${stringifyRequest(context.loaderContext, path.resolve(__dirname, '../runtime/hotModuleReplacement.cjs'))})(module.id, ${JSON.stringify(context.options)}, "${context.cssId ?? '0'}");
213
- // only invalidate when locals change
214
- if (
215
- module.hot.data &&
216
- module.hot.data.value &&
217
- module.hot.data.value !== localsJsonString
218
- ) {
219
- module.hot.invalidate();
220
- } else {
221
- module.hot.accept();
222
- }
223
- module.hot.dispose(function(data) {
224
- data.value = localsJsonString;
225
- cssReload();
226
- });
227
- })();
228
- }`;
229
- }
230
- function isCJSExports(exports) {
231
- return !exports.__esModule;
232
- }
233
- function isNamedExports(exports) {
234
- return !isCJSExports(exports)
235
- && (!exports.default || !('locals' in exports.default));
236
- }
237
- function parseQuery(query) {
238
- const params = new URLSearchParams(query);
239
- return Object.fromEntries(params);
240
- }
241
- //# sourceMappingURL=loader.js.map