diff options
Diffstat (limited to 'node_modules/webpack/lib/css')
| -rw-r--r-- | node_modules/webpack/lib/css/CssExportsGenerator.js | 203 | ||||
| -rw-r--r-- | node_modules/webpack/lib/css/CssGenerator.js | 151 | ||||
| -rw-r--r-- | node_modules/webpack/lib/css/CssLoadingRuntimeModule.js | 592 | ||||
| -rw-r--r-- | node_modules/webpack/lib/css/CssModulesPlugin.js | 888 | ||||
| -rw-r--r-- | node_modules/webpack/lib/css/CssParser.js | 1049 | ||||
| -rw-r--r-- | node_modules/webpack/lib/css/walkCssTokens.js | 775 |
6 files changed, 3658 insertions, 0 deletions
diff --git a/node_modules/webpack/lib/css/CssExportsGenerator.js b/node_modules/webpack/lib/css/CssExportsGenerator.js new file mode 100644 index 00000000..112aca22 --- /dev/null +++ b/node_modules/webpack/lib/css/CssExportsGenerator.js @@ -0,0 +1,203 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Sergey Melyukov @smelukov +*/ + +"use strict"; + +const { ReplaceSource, RawSource, ConcatSource } = require("webpack-sources"); +const { UsageState } = require("../ExportsInfo"); +const Generator = require("../Generator"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../../declarations/WebpackOptions").CssGeneratorExportsConvention} CssGeneratorExportsConvention */ +/** @typedef {import("../../declarations/WebpackOptions").CssGeneratorLocalIdentName} CssGeneratorLocalIdentName */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../DependencyTemplate").CssExportsData} CssExportsData */ +/** @typedef {import("../Generator").GenerateContext} GenerateContext */ +/** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */ +/** @typedef {import("../NormalModule")} NormalModule */ +/** @typedef {import("../util/Hash")} Hash */ + +/** + * @template T + * @typedef {import("../InitFragment")<T>} InitFragment + */ + +const TYPES = new Set(["javascript"]); + +class CssExportsGenerator extends Generator { + /** + * @param {CssGeneratorExportsConvention | undefined} convention the convention of the exports name + * @param {CssGeneratorLocalIdentName | undefined} localIdentName css export local ident name + * @param {boolean} esModule whether to use ES modules syntax + */ + constructor(convention, localIdentName, esModule) { + super(); + /** @type {CssGeneratorExportsConvention | undefined} */ + this.convention = convention; + /** @type {CssGeneratorLocalIdentName | undefined} */ + this.localIdentName = localIdentName; + /** @type {boolean} */ + this.esModule = esModule; + } + + /** + * @param {NormalModule} module module for which the bailout reason should be determined + * @param {ConcatenationBailoutReasonContext} context context + * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated + */ + getConcatenationBailoutReason(module, context) { + if (!this.esModule) { + return "Module is not an ECMAScript module"; + } + // TODO webpack 6: remove /\[moduleid\]/.test + if ( + /\[id\]/.test(this.localIdentName) || + /\[moduleid\]/.test(this.localIdentName) + ) { + return "The localIdentName includes moduleId ([id] or [moduleid])"; + } + return undefined; + } + + /** + * @param {NormalModule} module module for which the code should be generated + * @param {GenerateContext} generateContext context for generate + * @returns {Source} generated code + */ + generate(module, generateContext) { + const source = new ReplaceSource(new RawSource("")); + /** @type {InitFragment<TODO>[]} */ + const initFragments = []; + /** @type {CssExportsData} */ + const cssExportsData = { + esModule: this.esModule, + exports: new Map() + }; + + generateContext.runtimeRequirements.add(RuntimeGlobals.module); + + let chunkInitFragments; + const runtimeRequirements = new Set(); + + /** @type {DependencyTemplateContext} */ + const templateContext = { + runtimeTemplate: generateContext.runtimeTemplate, + dependencyTemplates: generateContext.dependencyTemplates, + moduleGraph: generateContext.moduleGraph, + chunkGraph: generateContext.chunkGraph, + module, + runtime: generateContext.runtime, + runtimeRequirements, + concatenationScope: generateContext.concatenationScope, + codeGenerationResults: generateContext.codeGenerationResults, + initFragments, + cssExportsData, + get chunkInitFragments() { + if (!chunkInitFragments) { + const data = generateContext.getData(); + chunkInitFragments = data.get("chunkInitFragments"); + if (!chunkInitFragments) { + chunkInitFragments = []; + data.set("chunkInitFragments", chunkInitFragments); + } + } + + return chunkInitFragments; + } + }; + + /** + * @param {Dependency} dependency the dependency + */ + const handleDependency = dependency => { + const constructor = /** @type {new (...args: any[]) => Dependency} */ ( + dependency.constructor + ); + const template = generateContext.dependencyTemplates.get(constructor); + if (!template) { + throw new Error( + `No template for dependency: ${dependency.constructor.name}` + ); + } + + template.apply(dependency, source, templateContext); + }; + + for (const dependency of module.dependencies) { + handleDependency(dependency); + } + + if (generateContext.concatenationScope) { + const source = new ConcatSource(); + const usedIdentifiers = new Set(); + for (const [name, v] of cssExportsData.exports) { + let identifier = Template.toIdentifier(name); + const i = 0; + while (usedIdentifiers.has(identifier)) { + identifier = Template.toIdentifier(name + i); + } + usedIdentifiers.add(identifier); + generateContext.concatenationScope.registerExport(name, identifier); + source.add( + `${ + generateContext.runtimeTemplate.supportsConst() ? "const" : "var" + } ${identifier} = ${JSON.stringify(v)};\n` + ); + } + return source; + } + const needNsObj = + this.esModule && + generateContext.moduleGraph + .getExportsInfo(module) + .otherExportsInfo.getUsed(generateContext.runtime) !== + UsageState.Unused; + if (needNsObj) { + generateContext.runtimeRequirements.add( + RuntimeGlobals.makeNamespaceObject + ); + } + const exports = []; + for (const [name, v] of cssExportsData.exports) { + exports.push(`\t${JSON.stringify(name)}: ${JSON.stringify(v)}`); + } + return new RawSource( + `${needNsObj ? `${RuntimeGlobals.makeNamespaceObject}(` : ""}${ + module.moduleArgument + }.exports = {\n${exports.join(",\n")}\n}${needNsObj ? ")" : ""};` + ); + } + + /** + * @param {NormalModule} module fresh module + * @returns {Set<string>} available types (do not mutate) + */ + getTypes(module) { + return TYPES; + } + + /** + * @param {NormalModule} module the module + * @param {string=} type source type + * @returns {number} estimate size of the module + */ + getSize(module, type) { + return 42; + } + + /** + * @param {Hash} hash hash that will be modified + * @param {UpdateHashContext} updateHashContext context for updating hash + */ + updateHash(hash, { module }) { + hash.update(this.esModule.toString()); + } +} + +module.exports = CssExportsGenerator; diff --git a/node_modules/webpack/lib/css/CssGenerator.js b/node_modules/webpack/lib/css/CssGenerator.js new file mode 100644 index 00000000..16f6ff16 --- /dev/null +++ b/node_modules/webpack/lib/css/CssGenerator.js @@ -0,0 +1,151 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Sergey Melyukov @smelukov +*/ + +"use strict"; + +const { ReplaceSource } = require("webpack-sources"); +const Generator = require("../Generator"); +const InitFragment = require("../InitFragment"); +const RuntimeGlobals = require("../RuntimeGlobals"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../../declarations/WebpackOptions").CssGeneratorExportsConvention} CssGeneratorExportsConvention */ +/** @typedef {import("../../declarations/WebpackOptions").CssGeneratorLocalIdentName} CssGeneratorLocalIdentName */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../DependencyTemplate").CssExportsData} CssExportsData */ +/** @typedef {import("../Generator").GenerateContext} GenerateContext */ +/** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../NormalModule")} NormalModule */ +/** @typedef {import("../util/Hash")} Hash */ + +const TYPES = new Set(["css"]); + +class CssGenerator extends Generator { + /** + * @param {CssGeneratorExportsConvention | undefined} convention the convention of the exports name + * @param {CssGeneratorLocalIdentName | undefined} localIdentName css export local ident name + * @param {boolean} esModule whether to use ES modules syntax + */ + constructor(convention, localIdentName, esModule) { + super(); + /** @type {CssGeneratorExportsConvention | undefined} */ + this.convention = convention; + /** @type {CssGeneratorLocalIdentName | undefined} */ + this.localIdentName = localIdentName; + /** @type {boolean} */ + this.esModule = esModule; + } + + /** + * @param {NormalModule} module module for which the code should be generated + * @param {GenerateContext} generateContext context for generate + * @returns {Source} generated code + */ + generate(module, generateContext) { + const originalSource = /** @type {Source} */ (module.originalSource()); + const source = new ReplaceSource(originalSource); + /** @type {InitFragment[]} */ + const initFragments = []; + /** @type {CssExportsData} */ + const cssExportsData = { + esModule: this.esModule, + exports: new Map() + }; + + generateContext.runtimeRequirements.add(RuntimeGlobals.hasCssModules); + + let chunkInitFragments; + /** @type {DependencyTemplateContext} */ + const templateContext = { + runtimeTemplate: generateContext.runtimeTemplate, + dependencyTemplates: generateContext.dependencyTemplates, + moduleGraph: generateContext.moduleGraph, + chunkGraph: generateContext.chunkGraph, + module, + runtime: generateContext.runtime, + runtimeRequirements: generateContext.runtimeRequirements, + concatenationScope: generateContext.concatenationScope, + codeGenerationResults: generateContext.codeGenerationResults, + initFragments, + cssExportsData, + get chunkInitFragments() { + if (!chunkInitFragments) { + const data = generateContext.getData(); + chunkInitFragments = data.get("chunkInitFragments"); + if (!chunkInitFragments) { + chunkInitFragments = []; + data.set("chunkInitFragments", chunkInitFragments); + } + } + + return chunkInitFragments; + } + }; + + /** + * @param {Dependency} dependency dependency + */ + const handleDependency = dependency => { + const constructor = /** @type {new (...args: any[]) => Dependency} */ ( + dependency.constructor + ); + const template = generateContext.dependencyTemplates.get(constructor); + if (!template) { + throw new Error( + `No template for dependency: ${dependency.constructor.name}` + ); + } + + template.apply(dependency, source, templateContext); + }; + for (const dependency of module.dependencies) { + handleDependency(dependency); + } + if (module.presentationalDependencies !== undefined) { + for (const dependency of module.presentationalDependencies) { + handleDependency(dependency); + } + } + + const data = generateContext.getData(); + data.set("css-exports", cssExportsData); + + return InitFragment.addToSource(source, initFragments, generateContext); + } + + /** + * @param {NormalModule} module fresh module + * @returns {Set<string>} available types (do not mutate) + */ + getTypes(module) { + return TYPES; + } + + /** + * @param {NormalModule} module the module + * @param {string=} type source type + * @returns {number} estimate size of the module + */ + getSize(module, type) { + const originalSource = module.originalSource(); + + if (!originalSource) { + return 0; + } + + return originalSource.size(); + } + + /** + * @param {Hash} hash hash that will be modified + * @param {UpdateHashContext} updateHashContext context for updating hash + */ + updateHash(hash, { module }) { + hash.update(this.esModule.toString()); + } +} + +module.exports = CssGenerator; diff --git a/node_modules/webpack/lib/css/CssLoadingRuntimeModule.js b/node_modules/webpack/lib/css/CssLoadingRuntimeModule.js new file mode 100644 index 00000000..b3e677ca --- /dev/null +++ b/node_modules/webpack/lib/css/CssLoadingRuntimeModule.js @@ -0,0 +1,592 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { SyncWaterfallHook } = require("tapable"); +const Compilation = require("../Compilation"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); +const compileBooleanMatcher = require("../util/compileBooleanMatcher"); +const { chunkHasCss } = require("./CssModulesPlugin"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Compilation").RuntimeRequirementsContext} RuntimeRequirementsContext */ +/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ + +/** + * @typedef {object} CssLoadingRuntimeModulePluginHooks + * @property {SyncWaterfallHook<[string, Chunk]>} createStylesheet + * @property {SyncWaterfallHook<[string, Chunk]>} linkPreload + * @property {SyncWaterfallHook<[string, Chunk]>} linkPrefetch + */ + +/** @type {WeakMap<Compilation, CssLoadingRuntimeModulePluginHooks>} */ +const compilationHooksMap = new WeakMap(); + +class CssLoadingRuntimeModule extends RuntimeModule { + /** + * @param {Compilation} compilation the compilation + * @returns {CssLoadingRuntimeModulePluginHooks} hooks + */ + static getCompilationHooks(compilation) { + if (!(compilation instanceof Compilation)) { + throw new TypeError( + "The 'compilation' argument must be an instance of Compilation" + ); + } + let hooks = compilationHooksMap.get(compilation); + if (hooks === undefined) { + hooks = { + createStylesheet: new SyncWaterfallHook(["source", "chunk"]), + linkPreload: new SyncWaterfallHook(["source", "chunk"]), + linkPrefetch: new SyncWaterfallHook(["source", "chunk"]) + }; + compilationHooksMap.set(compilation, hooks); + } + return hooks; + } + + /** + * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements + */ + constructor(runtimeRequirements) { + super("css loading", 10); + + this._runtimeRequirements = runtimeRequirements; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const { _runtimeRequirements } = this; + const compilation = /** @type {Compilation} */ (this.compilation); + const chunk = /** @type {Chunk} */ (this.chunk); + const { + chunkGraph, + runtimeTemplate, + outputOptions: { + crossOriginLoading, + uniqueName, + chunkLoadTimeout: loadTimeout, + cssHeadDataCompression: withCompression + } + } = compilation; + const fn = RuntimeGlobals.ensureChunkHandlers; + const conditionMap = chunkGraph.getChunkConditionMap( + /** @type {Chunk} */ (chunk), + /** + * @param {Chunk} chunk the chunk + * @param {ChunkGraph} chunkGraph the chunk graph + * @returns {boolean} true, if the chunk has css + */ + (chunk, chunkGraph) => + Boolean(chunkGraph.getChunkModulesIterableBySourceType(chunk, "css")) + ); + const hasCssMatcher = compileBooleanMatcher(conditionMap); + + const withLoading = + _runtimeRequirements.has(RuntimeGlobals.ensureChunkHandlers) && + hasCssMatcher !== false; + const withPrefetch = this._runtimeRequirements.has( + RuntimeGlobals.prefetchChunkHandlers + ); + const withPreload = this._runtimeRequirements.has( + RuntimeGlobals.preloadChunkHandlers + ); + /** @type {boolean} */ + const withHmr = _runtimeRequirements.has( + RuntimeGlobals.hmrDownloadUpdateHandlers + ); + /** @type {Set<number | string | null>} */ + const initialChunkIdsWithCss = new Set(); + /** @type {Set<number | string | null>} */ + const initialChunkIdsWithoutCss = new Set(); + for (const c of /** @type {Chunk} */ (chunk).getAllInitialChunks()) { + (chunkHasCss(c, chunkGraph) + ? initialChunkIdsWithCss + : initialChunkIdsWithoutCss + ).add(c.id); + } + + if (!withLoading && !withHmr && initialChunkIdsWithCss.size === 0) { + return null; + } + + const { linkPreload, linkPrefetch } = + CssLoadingRuntimeModule.getCompilationHooks(compilation); + + const withFetchPriority = _runtimeRequirements.has( + RuntimeGlobals.hasFetchPriority + ); + + const { createStylesheet } = + CssLoadingRuntimeModule.getCompilationHooks(compilation); + + const stateExpression = withHmr + ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_css` + : undefined; + + const code = Template.asString([ + "link = document.createElement('link');", + `if (${RuntimeGlobals.scriptNonce}) {`, + Template.indent( + `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});` + ), + "}", + uniqueName + ? 'link.setAttribute("data-webpack", uniqueName + ":" + key);' + : "", + withFetchPriority + ? Template.asString([ + "if(fetchPriority) {", + Template.indent( + 'link.setAttribute("fetchpriority", fetchPriority);' + ), + "}" + ]) + : "", + "link.setAttribute(loadingAttribute, 1);", + 'link.rel = "stylesheet";', + "link.href = url;", + crossOriginLoading + ? crossOriginLoading === "use-credentials" + ? 'link.crossOrigin = "use-credentials";' + : Template.asString([ + "if (link.href.indexOf(window.location.origin + '/') !== 0) {", + Template.indent( + `link.crossOrigin = ${JSON.stringify(crossOriginLoading)};` + ), + "}" + ]) + : "" + ]); + + /** @type {(str: string) => number} */ + const cc = str => str.charCodeAt(0); + const name = uniqueName + ? runtimeTemplate.concatenation( + "--webpack-", + { expr: "uniqueName" }, + "-", + { expr: "chunkId" } + ) + : runtimeTemplate.concatenation("--webpack-", { expr: "chunkId" }); + + return Template.asString([ + "// object to store loaded and loading chunks", + "// undefined = chunk not loaded, null = chunk preloaded/prefetched", + "// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded", + `var installedChunks = ${ + stateExpression ? `${stateExpression} = ${stateExpression} || ` : "" + }{${Array.from( + initialChunkIdsWithoutCss, + id => `${JSON.stringify(id)}:0` + ).join(",")}};`, + "", + uniqueName + ? `var uniqueName = ${JSON.stringify( + runtimeTemplate.outputOptions.uniqueName + )};` + : "// data-webpack is not used as build has no uniqueName", + `var loadCssChunkData = ${runtimeTemplate.basicFunction( + "target, link, chunkId", + [ + `var data, token = "", token2 = "", exports = {}, ${ + withHmr ? "moduleIds = [], " : "" + }name = ${name}, i, cc = 1;`, + "try {", + Template.indent([ + "if(!link) link = loadStylesheet(chunkId);", + // `link.sheet.rules` for legacy browsers + "var cssRules = link.sheet.cssRules || link.sheet.rules;", + "var j = cssRules.length - 1;", + "while(j > -1 && !data) {", + Template.indent([ + "var style = cssRules[j--].style;", + "if(!style) continue;", + "data = style.getPropertyValue(name);" + ]), + "}" + ]), + "}catch(e){}", + "if(!data) {", + Template.indent([ + "data = getComputedStyle(document.head).getPropertyValue(name);" + ]), + "}", + "if(!data) return [];", + withCompression + ? Template.asString([ + // LZW decode + `var map = {}, char = data[0], oldPhrase = char, decoded = char, code = 256, maxCode = ${"\uFFFF".charCodeAt( + 0 + )}, phrase;`, + "for (i = 1; i < data.length; i++) {", + Template.indent([ + "cc = data[i].charCodeAt(0);", + "if (cc < 256) phrase = data[i]; else phrase = map[cc] ? map[cc] : (oldPhrase + char);", + "decoded += phrase;", + "char = phrase.charAt(0);", + "map[code] = oldPhrase + char;", + "if (++code > maxCode) { code = 256; map = {}; }", + "oldPhrase = phrase;" + ]), + "}", + "data = decoded;" + ]) + : "// css head data compression is disabled", + "for(i = 0; cc; i++) {", + Template.indent([ + "cc = data.charCodeAt(i);", + `if(cc == ${cc(":")}) { token2 = token; token = ""; }`, + `else if(cc == ${cc( + "/" + )}) { token = token.replace(/^_/, ""); token2 = token2.replace(/^_/, ""); exports[token2] = token; token = ""; token2 = ""; }`, + `else if(cc == ${cc("&")}) { ${ + RuntimeGlobals.makeNamespaceObject + }(exports); }`, + `else if(!cc || cc == ${cc( + "," + )}) { token = token.replace(/^_/, ""); target[token] = (${runtimeTemplate.basicFunction( + "exports, module", + "module.exports = exports;" + )}).bind(null, exports); ${ + withHmr ? "moduleIds.push(token); " : "" + }token = ""; token2 = ""; exports = {}; }`, + `else if(cc == ${cc("\\")}) { token += data[++i] }`, + "else { token += data[i]; }" + ]), + "}", + `${ + withHmr ? `if(target == ${RuntimeGlobals.moduleFactories}) ` : "" + }installedChunks[chunkId] = 0;`, + withHmr ? "return moduleIds;" : "" + ] + )}`, + 'var loadingAttribute = "data-webpack-loading";', + `var loadStylesheet = ${runtimeTemplate.basicFunction( + `chunkId, url, done${withHmr ? ", hmr" : ""}${ + withFetchPriority ? ", fetchPriority" : "" + }`, + [ + 'var link, needAttach, key = "chunk-" + chunkId;', + withHmr ? "if(!hmr) {" : "", + 'var links = document.getElementsByTagName("link");', + "for(var i = 0; i < links.length; i++) {", + Template.indent([ + "var l = links[i];", + `if(l.rel == "stylesheet" && (${ + withHmr + ? 'l.href.startsWith(url) || l.getAttribute("href").startsWith(url)' + : 'l.href == url || l.getAttribute("href") == url' + }${ + uniqueName + ? ' || l.getAttribute("data-webpack") == uniqueName + ":" + key' + : "" + })) { link = l; break; }` + ]), + "}", + "if(!done) return link;", + withHmr ? "}" : "", + "if(!link) {", + Template.indent([ + "needAttach = true;", + createStylesheet.call(code, /** @type {Chunk} */ (this.chunk)) + ]), + "}", + `var onLinkComplete = ${runtimeTemplate.basicFunction( + "prev, event", + Template.asString([ + "link.onerror = link.onload = null;", + "link.removeAttribute(loadingAttribute);", + "clearTimeout(timeout);", + 'if(event && event.type != "load") link.parentNode.removeChild(link)', + "done(event);", + "if(prev) return prev(event);" + ]) + )};`, + "if(link.getAttribute(loadingAttribute)) {", + Template.indent([ + `var timeout = setTimeout(onLinkComplete.bind(null, undefined, { type: 'timeout', target: link }), ${loadTimeout});`, + "link.onerror = onLinkComplete.bind(null, link.onerror);", + "link.onload = onLinkComplete.bind(null, link.onload);" + ]), + "} else onLinkComplete(undefined, { type: 'load', target: link });", // We assume any existing stylesheet is render blocking + withHmr ? "hmr ? document.head.insertBefore(link, hmr) :" : "", + "needAttach && document.head.appendChild(link);", + "return link;" + ] + )};`, + initialChunkIdsWithCss.size > 2 + ? `${JSON.stringify( + Array.from(initialChunkIdsWithCss) + )}.forEach(loadCssChunkData.bind(null, ${ + RuntimeGlobals.moduleFactories + }, 0));` + : initialChunkIdsWithCss.size > 0 + ? `${Array.from( + initialChunkIdsWithCss, + id => + `loadCssChunkData(${ + RuntimeGlobals.moduleFactories + }, 0, ${JSON.stringify(id)});` + ).join("")}` + : "// no initial css", + "", + withLoading + ? Template.asString([ + `${fn}.css = ${runtimeTemplate.basicFunction( + `chunkId, promises${withFetchPriority ? " , fetchPriority" : ""}`, + [ + "// css chunk loading", + `var installedChunkData = ${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;`, + 'if(installedChunkData !== 0) { // 0 means "already installed".', + Template.indent([ + "", + '// a Promise means "currently loading".', + "if(installedChunkData) {", + Template.indent(["promises.push(installedChunkData[2]);"]), + "} else {", + Template.indent([ + hasCssMatcher === true + ? "if(true) { // all chunks have CSS" + : `if(${hasCssMatcher("chunkId")}) {`, + Template.indent([ + "// setup Promise in chunk cache", + `var promise = new Promise(${runtimeTemplate.expressionFunction( + "installedChunkData = installedChunks[chunkId] = [resolve, reject]", + "resolve, reject" + )});`, + "promises.push(installedChunkData[2] = promise);", + "", + "// start chunk loading", + `var url = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkCssFilename}(chunkId);`, + "// create error before stack unwound to get useful stacktrace later", + "var error = new Error();", + `var loadingEnded = ${runtimeTemplate.basicFunction( + "event", + [ + `if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId)) {`, + Template.indent([ + "installedChunkData = installedChunks[chunkId];", + "if(installedChunkData !== 0) installedChunks[chunkId] = undefined;", + "if(installedChunkData) {", + Template.indent([ + 'if(event.type !== "load") {', + Template.indent([ + "var errorType = event && event.type;", + "var realHref = event && event.target && event.target.href;", + "error.message = 'Loading css chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realHref + ')';", + "error.name = 'ChunkLoadError';", + "error.type = errorType;", + "error.request = realHref;", + "installedChunkData[1](error);" + ]), + "} else {", + Template.indent([ + `loadCssChunkData(${RuntimeGlobals.moduleFactories}, link, chunkId);`, + "installedChunkData[0]();" + ]), + "}" + ]), + "}" + ]), + "}" + ] + )};`, + `var link = loadStylesheet(chunkId, url, loadingEnded${ + withFetchPriority ? ", fetchPriority" : "" + });` + ]), + "} else installedChunks[chunkId] = 0;" + ]), + "}" + ]), + "}" + ] + )};` + ]) + : "// no chunk loading", + "", + withPrefetch && hasCssMatcher !== false + ? `${ + RuntimeGlobals.prefetchChunkHandlers + }.s = ${runtimeTemplate.basicFunction("chunkId", [ + `if((!${ + RuntimeGlobals.hasOwnProperty + }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${ + hasCssMatcher === true ? "true" : hasCssMatcher("chunkId") + }) {`, + Template.indent([ + "installedChunks[chunkId] = null;", + linkPrefetch.call( + Template.asString([ + "var link = document.createElement('link');", + crossOriginLoading + ? `link.crossOrigin = ${JSON.stringify( + crossOriginLoading + )};` + : "", + `if (${RuntimeGlobals.scriptNonce}) {`, + Template.indent( + `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});` + ), + "}", + 'link.rel = "prefetch";', + 'link.as = "style";', + `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkCssFilename}(chunkId);` + ]), + chunk + ), + "document.head.appendChild(link);" + ]), + "}" + ])};` + : "// no prefetching", + "", + withPreload && hasCssMatcher !== false + ? `${ + RuntimeGlobals.preloadChunkHandlers + }.s = ${runtimeTemplate.basicFunction("chunkId", [ + `if((!${ + RuntimeGlobals.hasOwnProperty + }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${ + hasCssMatcher === true ? "true" : hasCssMatcher("chunkId") + }) {`, + Template.indent([ + "installedChunks[chunkId] = null;", + linkPreload.call( + Template.asString([ + "var link = document.createElement('link');", + "link.charset = 'utf-8';", + `if (${RuntimeGlobals.scriptNonce}) {`, + Template.indent( + `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});` + ), + "}", + 'link.rel = "preload";', + 'link.as = "style";', + `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkCssFilename}(chunkId);`, + crossOriginLoading + ? crossOriginLoading === "use-credentials" + ? 'link.crossOrigin = "use-credentials";' + : Template.asString([ + "if (link.href.indexOf(window.location.origin + '/') !== 0) {", + Template.indent( + `link.crossOrigin = ${JSON.stringify( + crossOriginLoading + )};` + ), + "}" + ]) + : "" + ]), + chunk + ), + "document.head.appendChild(link);" + ]), + "}" + ])};` + : "// no preloaded", + withHmr + ? Template.asString([ + "var oldTags = [];", + "var newTags = [];", + `var applyHandler = ${runtimeTemplate.basicFunction("options", [ + `return { dispose: ${runtimeTemplate.basicFunction( + "", + [] + )}, apply: ${runtimeTemplate.basicFunction("", [ + "var moduleIds = [];", + `newTags.forEach(${runtimeTemplate.expressionFunction( + "info[1].sheet.disabled = false", + "info" + )});`, + "while(oldTags.length) {", + Template.indent([ + "var oldTag = oldTags.pop();", + "if(oldTag.parentNode) oldTag.parentNode.removeChild(oldTag);" + ]), + "}", + "while(newTags.length) {", + Template.indent([ + "var info = newTags.pop();", + `var chunkModuleIds = loadCssChunkData(${RuntimeGlobals.moduleFactories}, info[1], info[0]);`, + `chunkModuleIds.forEach(${runtimeTemplate.expressionFunction( + "moduleIds.push(id)", + "id" + )});` + ]), + "}", + "return moduleIds;" + ])} };` + ])}`, + `var cssTextKey = ${runtimeTemplate.returningFunction( + `Array.from(link.sheet.cssRules, ${runtimeTemplate.returningFunction( + "r.cssText", + "r" + )}).join()`, + "link" + )}`, + `${ + RuntimeGlobals.hmrDownloadUpdateHandlers + }.css = ${runtimeTemplate.basicFunction( + "chunkIds, removedChunks, removedModules, promises, applyHandlers, updatedModulesList", + [ + "applyHandlers.push(applyHandler);", + `chunkIds.forEach(${runtimeTemplate.basicFunction("chunkId", [ + `var filename = ${RuntimeGlobals.getChunkCssFilename}(chunkId);`, + `var url = ${RuntimeGlobals.publicPath} + filename;`, + "var oldTag = loadStylesheet(chunkId, url);", + "if(!oldTag) return;", + `promises.push(new Promise(${runtimeTemplate.basicFunction( + "resolve, reject", + [ + `var link = loadStylesheet(chunkId, url + (url.indexOf("?") < 0 ? "?" : "&") + "hmr=" + Date.now(), ${runtimeTemplate.basicFunction( + "event", + [ + 'if(event.type !== "load") {', + Template.indent([ + "var errorType = event && event.type;", + "var realHref = event && event.target && event.target.href;", + "error.message = 'Loading css hot update chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realHref + ')';", + "error.name = 'ChunkLoadError';", + "error.type = errorType;", + "error.request = realHref;", + "reject(error);" + ]), + "} else {", + Template.indent([ + "try { if(cssTextKey(oldTag) == cssTextKey(link)) { if(link.parentNode) link.parentNode.removeChild(link); return resolve(); } } catch(e) {}", + "var factories = {};", + "loadCssChunkData(factories, link, chunkId);", + `Object.keys(factories).forEach(${runtimeTemplate.expressionFunction( + "updatedModulesList.push(id)", + "id" + )})`, + "link.sheet.disabled = true;", + "oldTags.push(oldTag);", + "newTags.push([chunkId, link]);", + "resolve();" + ]), + "}" + ] + )}, oldTag);` + ] + )}));` + ])});` + ] + )}` + ]) + : "// no hmr" + ]); + } +} + +module.exports = CssLoadingRuntimeModule; diff --git a/node_modules/webpack/lib/css/CssModulesPlugin.js b/node_modules/webpack/lib/css/CssModulesPlugin.js new file mode 100644 index 00000000..213c2178 --- /dev/null +++ b/node_modules/webpack/lib/css/CssModulesPlugin.js @@ -0,0 +1,888 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { SyncWaterfallHook, SyncHook } = require("tapable"); +const { + ConcatSource, + PrefixSource, + ReplaceSource, + CachedSource +} = require("webpack-sources"); +const Compilation = require("../Compilation"); +const CssModule = require("../CssModule"); +const { tryRunOrWebpackError } = require("../HookWebpackError"); +const HotUpdateChunk = require("../HotUpdateChunk"); +const { + CSS_MODULE_TYPE, + CSS_MODULE_TYPE_GLOBAL, + CSS_MODULE_TYPE_MODULE, + CSS_MODULE_TYPE_AUTO +} = require("../ModuleTypeConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const SelfModuleFactory = require("../SelfModuleFactory"); +const WebpackError = require("../WebpackError"); +const CssExportDependency = require("../dependencies/CssExportDependency"); +const CssImportDependency = require("../dependencies/CssImportDependency"); +const CssLocalIdentifierDependency = require("../dependencies/CssLocalIdentifierDependency"); +const CssSelfLocalIdentifierDependency = require("../dependencies/CssSelfLocalIdentifierDependency"); +const CssUrlDependency = require("../dependencies/CssUrlDependency"); +const StaticExportsDependency = require("../dependencies/StaticExportsDependency"); +const { compareModulesByIdentifier } = require("../util/comparators"); +const createSchemaValidation = require("../util/create-schema-validation"); +const createHash = require("../util/createHash"); +const { getUndoPath } = require("../util/identifier"); +const memoize = require("../util/memoize"); +const nonNumericOnlyHash = require("../util/nonNumericOnlyHash"); +const CssExportsGenerator = require("./CssExportsGenerator"); +const CssGenerator = require("./CssGenerator"); +const CssParser = require("./CssParser"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../../declarations/WebpackOptions").OutputNormalized} OutputOptions */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */ +/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../CssModule").Inheritance} Inheritance */ +/** @typedef {import("../DependencyTemplate").CssExportsData} CssExportsData */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Template").RuntimeTemplate} RuntimeTemplate */ +/** @typedef {import("../TemplatedPathPlugin").TemplatePath} TemplatePath */ +/** @typedef {import("../util/Hash")} Hash */ +/** @typedef {import("../util/memoize")} Memoize */ + +/** + * @typedef {object} ChunkRenderContext + * @property {RuntimeTemplate} runtimeTemplate runtime template + */ + +/** + * @typedef {object} CompilationHooks + * @property {SyncWaterfallHook<[Source, Module, ChunkRenderContext]>} renderModulePackage + * @property {SyncHook<[Chunk, Hash, ChunkHashContext]>} chunkHash + */ + +const getCssLoadingRuntimeModule = memoize(() => + require("./CssLoadingRuntimeModule") +); + +/** + * @param {string} name name + * @returns {{oneOf: [{$ref: string}], definitions: *}} schema + */ +const getSchema = name => { + const { definitions } = require("../../schemas/WebpackOptions.json"); + return { + definitions, + oneOf: [{ $ref: `#/definitions/${name}` }] + }; +}; + +const generatorValidationOptions = { + name: "Css Modules Plugin", + baseDataPath: "generator" +}; +const validateGeneratorOptions = { + css: createSchemaValidation( + require("../../schemas/plugins/css/CssGeneratorOptions.check.js"), + () => getSchema("CssGeneratorOptions"), + generatorValidationOptions + ), + "css/auto": createSchemaValidation( + require("../../schemas/plugins/css/CssAutoGeneratorOptions.check.js"), + () => getSchema("CssAutoGeneratorOptions"), + generatorValidationOptions + ), + "css/module": createSchemaValidation( + require("../../schemas/plugins/css/CssModuleGeneratorOptions.check.js"), + () => getSchema("CssModuleGeneratorOptions"), + generatorValidationOptions + ), + "css/global": createSchemaValidation( + require("../../schemas/plugins/css/CssGlobalGeneratorOptions.check.js"), + () => getSchema("CssGlobalGeneratorOptions"), + generatorValidationOptions + ) +}; + +const parserValidationOptions = { + name: "Css Modules Plugin", + baseDataPath: "parser" +}; +const validateParserOptions = { + css: createSchemaValidation( + require("../../schemas/plugins/css/CssParserOptions.check.js"), + () => getSchema("CssParserOptions"), + parserValidationOptions + ), + "css/auto": createSchemaValidation( + require("../../schemas/plugins/css/CssAutoParserOptions.check.js"), + () => getSchema("CssAutoParserOptions"), + parserValidationOptions + ), + "css/module": createSchemaValidation( + require("../../schemas/plugins/css/CssModuleParserOptions.check.js"), + () => getSchema("CssModuleParserOptions"), + parserValidationOptions + ), + "css/global": createSchemaValidation( + require("../../schemas/plugins/css/CssGlobalParserOptions.check.js"), + () => getSchema("CssGlobalParserOptions"), + parserValidationOptions + ) +}; + +/** @type {WeakMap<Compilation, CompilationHooks>} */ +const compilationHooksMap = new WeakMap(); + +/** + * @param {string} str string + * @param {boolean=} omitOptionalUnderscore if true, optional underscore is not added + * @returns {string} escaped string + */ +const escapeCss = (str, omitOptionalUnderscore) => { + const escaped = `${str}`.replace( + // cspell:word uffff + /[^a-zA-Z0-9_\u0081-\uFFFF-]/g, + s => `\\${s}` + ); + return !omitOptionalUnderscore && /^(?!--)[0-9_-]/.test(escaped) + ? `_${escaped}` + : escaped; +}; + +/** + * @param {string} str string + * @returns {string} encoded string + */ +const lzwEncode = str => { + /** @type {Map<string, string>} */ + const map = new Map(); + let encoded = ""; + let phrase = str[0]; + let code = 256; + const maxCode = "\uFFFF".charCodeAt(0); + for (let i = 1; i < str.length; i++) { + const c = str[i]; + if (map.has(phrase + c)) { + phrase += c; + } else { + encoded += phrase.length > 1 ? map.get(phrase) : phrase; + map.set(phrase + c, String.fromCharCode(code)); + phrase = c; + if (++code > maxCode) { + code = 256; + map.clear(); + } + } + } + encoded += phrase.length > 1 ? map.get(phrase) : phrase; + return encoded; +}; + +const PLUGIN_NAME = "CssModulesPlugin"; + +class CssModulesPlugin { + /** + * @param {Compilation} compilation the compilation + * @returns {CompilationHooks} the attached hooks + */ + static getCompilationHooks(compilation) { + if (!(compilation instanceof Compilation)) { + throw new TypeError( + "The 'compilation' argument must be an instance of Compilation" + ); + } + let hooks = compilationHooksMap.get(compilation); + if (hooks === undefined) { + hooks = { + renderModulePackage: new SyncWaterfallHook([ + "source", + "module", + "renderContext" + ]), + chunkHash: new SyncHook(["chunk", "hash", "context"]) + }; + compilationHooksMap.set(compilation, hooks); + } + return hooks; + } + + constructor() { + /** @type {WeakMap<Source, { undoPath: string, inheritance: Inheritance, source: CachedSource }>} */ + this._moduleCache = new WeakMap(); + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + const hooks = CssModulesPlugin.getCompilationHooks(compilation); + const selfFactory = new SelfModuleFactory(compilation.moduleGraph); + compilation.dependencyFactories.set( + CssUrlDependency, + normalModuleFactory + ); + compilation.dependencyTemplates.set( + CssUrlDependency, + new CssUrlDependency.Template() + ); + compilation.dependencyTemplates.set( + CssLocalIdentifierDependency, + new CssLocalIdentifierDependency.Template() + ); + compilation.dependencyFactories.set( + CssSelfLocalIdentifierDependency, + selfFactory + ); + compilation.dependencyTemplates.set( + CssSelfLocalIdentifierDependency, + new CssSelfLocalIdentifierDependency.Template() + ); + compilation.dependencyTemplates.set( + CssExportDependency, + new CssExportDependency.Template() + ); + compilation.dependencyFactories.set( + CssImportDependency, + normalModuleFactory + ); + compilation.dependencyTemplates.set( + CssImportDependency, + new CssImportDependency.Template() + ); + compilation.dependencyTemplates.set( + StaticExportsDependency, + new StaticExportsDependency.Template() + ); + for (const type of [ + CSS_MODULE_TYPE, + CSS_MODULE_TYPE_GLOBAL, + CSS_MODULE_TYPE_MODULE, + CSS_MODULE_TYPE_AUTO + ]) { + normalModuleFactory.hooks.createParser + .for(type) + .tap(PLUGIN_NAME, parserOptions => { + validateParserOptions[type](parserOptions); + const { namedExports } = parserOptions; + + switch (type) { + case CSS_MODULE_TYPE_GLOBAL: + case CSS_MODULE_TYPE_AUTO: + return new CssParser({ + namedExports + }); + case CSS_MODULE_TYPE: + return new CssParser({ + allowModeSwitch: false, + namedExports + }); + case CSS_MODULE_TYPE_MODULE: + return new CssParser({ + defaultMode: "local", + namedExports + }); + } + }); + normalModuleFactory.hooks.createGenerator + .for(type) + .tap(PLUGIN_NAME, generatorOptions => { + validateGeneratorOptions[type](generatorOptions); + + return generatorOptions.exportsOnly + ? new CssExportsGenerator( + generatorOptions.exportsConvention, + generatorOptions.localIdentName, + generatorOptions.esModule + ) + : new CssGenerator( + generatorOptions.exportsConvention, + generatorOptions.localIdentName, + generatorOptions.esModule + ); + }); + normalModuleFactory.hooks.createModuleClass + .for(type) + .tap(PLUGIN_NAME, (createData, resolveData) => { + if (resolveData.dependencies.length > 0) { + // When CSS is imported from CSS there is only one dependency + const dependency = resolveData.dependencies[0]; + + if (dependency instanceof CssImportDependency) { + const parent = + /** @type {CssModule} */ + (compilation.moduleGraph.getParentModule(dependency)); + + if (parent instanceof CssModule) { + /** @type {import("../CssModule").Inheritance | undefined} */ + let inheritance; + + if ( + (parent.cssLayer !== null && + parent.cssLayer !== undefined) || + parent.supports || + parent.media + ) { + if (!inheritance) { + inheritance = []; + } + + inheritance.push([ + parent.cssLayer, + parent.supports, + parent.media + ]); + } + + if (parent.inheritance) { + if (!inheritance) { + inheritance = []; + } + + inheritance.push(...parent.inheritance); + } + + return new CssModule({ + ...createData, + cssLayer: dependency.layer, + supports: dependency.supports, + media: dependency.media, + inheritance + }); + } + + return new CssModule({ + ...createData, + cssLayer: dependency.layer, + supports: dependency.supports, + media: dependency.media + }); + } + } + + return new CssModule(createData); + }); + } + const orderedCssModulesPerChunk = new WeakMap(); + compilation.hooks.afterCodeGeneration.tap("CssModulesPlugin", () => { + const { chunkGraph } = compilation; + for (const chunk of compilation.chunks) { + if (CssModulesPlugin.chunkHasCss(chunk, chunkGraph)) { + orderedCssModulesPerChunk.set( + chunk, + this.getOrderedChunkCssModules(chunk, chunkGraph, compilation) + ); + } + } + }); + compilation.hooks.chunkHash.tap( + "CssModulesPlugin", + (chunk, hash, context) => { + hooks.chunkHash.call(chunk, hash, context); + } + ); + compilation.hooks.contentHash.tap("CssModulesPlugin", chunk => { + const { + chunkGraph, + codeGenerationResults, + moduleGraph, + runtimeTemplate, + outputOptions: { + hashSalt, + hashDigest, + hashDigestLength, + hashFunction + } + } = compilation; + const hash = createHash(hashFunction); + if (hashSalt) hash.update(hashSalt); + hooks.chunkHash.call(chunk, hash, { + chunkGraph, + codeGenerationResults, + moduleGraph, + runtimeTemplate + }); + const modules = orderedCssModulesPerChunk.get(chunk); + if (modules) { + for (const module of modules) { + hash.update(chunkGraph.getModuleHash(module, chunk.runtime)); + } + } + const digest = /** @type {string} */ (hash.digest(hashDigest)); + chunk.contentHash.css = nonNumericOnlyHash(digest, hashDigestLength); + }); + compilation.hooks.renderManifest.tap(PLUGIN_NAME, (result, options) => { + const { chunkGraph } = compilation; + const { hash, chunk, codeGenerationResults, runtimeTemplate } = + options; + + if (chunk instanceof HotUpdateChunk) return result; + + /** @type {CssModule[] | undefined} */ + const modules = orderedCssModulesPerChunk.get(chunk); + if (modules !== undefined) { + const { path: filename, info } = compilation.getPathWithInfo( + CssModulesPlugin.getChunkFilenameTemplate( + chunk, + compilation.outputOptions + ), + { + hash, + runtime: chunk.runtime, + chunk, + contentHashType: "css" + } + ); + const undoPath = getUndoPath( + filename, + compilation.outputOptions.path, + false + ); + result.push({ + render: () => + this.renderChunk({ + chunk, + chunkGraph, + codeGenerationResults, + uniqueName: compilation.outputOptions.uniqueName, + cssHeadDataCompression: + compilation.outputOptions.cssHeadDataCompression, + undoPath, + modules, + runtimeTemplate, + hooks + }), + filename, + info, + identifier: `css${chunk.id}`, + hash: chunk.contentHash.css + }); + } + return result; + }); + const globalChunkLoading = compilation.outputOptions.chunkLoading; + /** + * @param {Chunk} chunk the chunk + * @returns {boolean} true, when enabled + */ + const isEnabledForChunk = chunk => { + const options = chunk.getEntryOptions(); + const chunkLoading = + options && options.chunkLoading !== undefined + ? options.chunkLoading + : globalChunkLoading; + return chunkLoading === "jsonp" || chunkLoading === "import"; + }; + const onceForChunkSet = new WeakSet(); + /** + * @param {Chunk} chunk chunk to check + * @param {Set<string>} set runtime requirements + */ + const handler = (chunk, set) => { + if (onceForChunkSet.has(chunk)) return; + onceForChunkSet.add(chunk); + if (!isEnabledForChunk(chunk)) return; + + set.add(RuntimeGlobals.publicPath); + set.add(RuntimeGlobals.getChunkCssFilename); + set.add(RuntimeGlobals.hasOwnProperty); + set.add(RuntimeGlobals.moduleFactoriesAddOnly); + set.add(RuntimeGlobals.makeNamespaceObject); + + const CssLoadingRuntimeModule = getCssLoadingRuntimeModule(); + compilation.addRuntimeModule(chunk, new CssLoadingRuntimeModule(set)); + }; + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.hasCssModules) + .tap(PLUGIN_NAME, handler); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.ensureChunkHandlers) + .tap(PLUGIN_NAME, handler); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.hmrDownloadUpdateHandlers) + .tap(PLUGIN_NAME, handler); + } + ); + } + + /** + * @param {Chunk} chunk chunk + * @param {Iterable<Module>} modules unordered modules + * @param {Compilation} compilation compilation + * @returns {Module[]} ordered modules + */ + getModulesInOrder(chunk, modules, compilation) { + if (!modules) return []; + + /** @type {Module[]} */ + const modulesList = [...modules]; + + // Get ordered list of modules per chunk group + // Lists are in reverse order to allow to use Array.pop() + const modulesByChunkGroup = Array.from(chunk.groupsIterable, chunkGroup => { + const sortedModules = modulesList + .map(module => ({ + module, + index: chunkGroup.getModulePostOrderIndex(module) + })) + .filter(item => item.index !== undefined) + .sort( + (a, b) => + /** @type {number} */ (b.index) - /** @type {number} */ (a.index) + ) + .map(item => item.module); + + return { list: sortedModules, set: new Set(sortedModules) }; + }); + + if (modulesByChunkGroup.length === 1) + return modulesByChunkGroup[0].list.reverse(); + + const compareModuleLists = ({ list: a }, { list: b }) => { + if (a.length === 0) { + return b.length === 0 ? 0 : 1; + } + if (b.length === 0) return -1; + return compareModulesByIdentifier(a[a.length - 1], b[b.length - 1]); + }; + + modulesByChunkGroup.sort(compareModuleLists); + + /** @type {Module[]} */ + const finalModules = []; + + for (;;) { + const failedModules = new Set(); + const list = modulesByChunkGroup[0].list; + if (list.length === 0) { + // done, everything empty + break; + } + /** @type {Module} */ + let selectedModule = list[list.length - 1]; + let hasFailed; + outer: for (;;) { + for (const { list, set } of modulesByChunkGroup) { + if (list.length === 0) continue; + const lastModule = list[list.length - 1]; + if (lastModule === selectedModule) continue; + if (!set.has(selectedModule)) continue; + failedModules.add(selectedModule); + if (failedModules.has(lastModule)) { + // There is a conflict, try other alternatives + hasFailed = lastModule; + continue; + } + selectedModule = lastModule; + hasFailed = false; + continue outer; // restart + } + break; + } + if (hasFailed) { + // There is a not resolve-able conflict with the selectedModule + // TODO print better warning + compilation.warnings.push( + new WebpackError( + `chunk ${chunk.name || chunk.id}\nConflicting order between ${ + /** @type {Module} */ + (hasFailed).readableIdentifier(compilation.requestShortener) + } and ${selectedModule.readableIdentifier( + compilation.requestShortener + )}` + ) + ); + selectedModule = /** @type {Module} */ (hasFailed); + } + // Insert the selected module into the final modules list + finalModules.push(selectedModule); + // Remove the selected module from all lists + for (const { list, set } of modulesByChunkGroup) { + const lastModule = list[list.length - 1]; + if (lastModule === selectedModule) list.pop(); + else if (hasFailed && set.has(selectedModule)) { + const idx = list.indexOf(selectedModule); + if (idx >= 0) list.splice(idx, 1); + } + } + modulesByChunkGroup.sort(compareModuleLists); + } + return finalModules; + } + + /** + * @param {Chunk} chunk chunk + * @param {ChunkGraph} chunkGraph chunk graph + * @param {Compilation} compilation compilation + * @returns {Module[]} ordered css modules + */ + getOrderedChunkCssModules(chunk, chunkGraph, compilation) { + return [ + ...this.getModulesInOrder( + chunk, + /** @type {Iterable<Module>} */ + ( + chunkGraph.getOrderedChunkModulesIterableBySourceType( + chunk, + "css-import", + compareModulesByIdentifier + ) + ), + compilation + ), + ...this.getModulesInOrder( + chunk, + /** @type {Iterable<Module>} */ + ( + chunkGraph.getOrderedChunkModulesIterableBySourceType( + chunk, + "css", + compareModulesByIdentifier + ) + ), + compilation + ) + ]; + } + + /** + * @param {object} options options + * @param {string[]} options.metaData meta data + * @param {string} options.undoPath undo path for public path auto + * @param {Chunk} options.chunk chunk + * @param {ChunkGraph} options.chunkGraph chunk graph + * @param {CodeGenerationResults} options.codeGenerationResults code generation results + * @param {CssModule} options.module css module + * @param {RuntimeTemplate} options.runtimeTemplate runtime template + * @param {CompilationHooks} options.hooks hooks + * @returns {Source} css module source + */ + renderModule({ + metaData, + undoPath, + chunk, + chunkGraph, + codeGenerationResults, + module, + hooks, + runtimeTemplate + }) { + const codeGenResult = codeGenerationResults.get(module, chunk.runtime); + const moduleSourceContent = + /** @type {Source} */ + ( + codeGenResult.sources.get("css") || + codeGenResult.sources.get("css-import") + ); + + const cacheEntry = this._moduleCache.get(moduleSourceContent); + + /** @type {Inheritance} */ + const inheritance = [[module.cssLayer, module.supports, module.media]]; + if (module.inheritance) { + inheritance.push(...module.inheritance); + } + + let source; + if ( + cacheEntry && + cacheEntry.undoPath === undoPath && + cacheEntry.inheritance.every(([layer, supports, media], i) => { + const item = inheritance[i]; + if (Array.isArray(item)) { + return layer === item[0] && supports === item[1] && media === item[2]; + } + return false; + }) + ) { + source = cacheEntry.source; + } else { + const moduleSourceCode = /** @type {string} */ ( + moduleSourceContent.source() + ); + const publicPathAutoRegex = new RegExp( + CssUrlDependency.PUBLIC_PATH_AUTO, + "g" + ); + /** @type {Source} */ + let moduleSource = new ReplaceSource(moduleSourceContent); + let match; + while ((match = publicPathAutoRegex.exec(moduleSourceCode))) { + /** @type {ReplaceSource} */ (moduleSource).replace( + match.index, + (match.index += match[0].length - 1), + undoPath + ); + } + + for (let i = 0; i < inheritance.length; i++) { + const layer = inheritance[i][0]; + const supports = inheritance[i][1]; + const media = inheritance[i][2]; + + if (media) { + moduleSource = new ConcatSource( + `@media ${media} {\n`, + new PrefixSource("\t", moduleSource), + "}\n" + ); + } + + if (supports) { + moduleSource = new ConcatSource( + `@supports (${supports}) {\n`, + new PrefixSource("\t", moduleSource), + "}\n" + ); + } + + // Layer can be anonymous + if (layer !== undefined && layer !== null) { + moduleSource = new ConcatSource( + `@layer${layer ? ` ${layer}` : ""} {\n`, + new PrefixSource("\t", moduleSource), + "}\n" + ); + } + } + + if (moduleSource) { + moduleSource = new ConcatSource(moduleSource, "\n"); + } + + source = new CachedSource(moduleSource); + this._moduleCache.set(moduleSourceContent, { + inheritance, + undoPath, + source + }); + } + /** @type {CssExportsData | undefined} */ + const cssExportsData = + codeGenResult.data && codeGenResult.data.get("css-exports"); + const exports = cssExportsData && cssExportsData.exports; + const esModule = cssExportsData && cssExportsData.esModule; + let moduleId = String(chunkGraph.getModuleId(module)); + + // When `optimization.moduleIds` is `named` the module id is a path, so we need to normalize it between platforms + if (typeof moduleId === "string") { + moduleId = moduleId.replace(/\\/g, "/"); + } + + metaData.push( + `${ + exports + ? Array.from( + exports, + ([n, v]) => `${escapeCss(n)}:${escapeCss(v)}/` + ).join("") + : "" + }${esModule ? "&" : ""}${escapeCss(moduleId)}` + ); + return tryRunOrWebpackError( + () => + hooks.renderModulePackage.call(source, module, { + runtimeTemplate + }), + "CssModulesPlugin.getCompilationHooks().renderModulePackage" + ); + } + + /** + * @param {object} options options + * @param {string | undefined} options.uniqueName unique name + * @param {boolean | undefined} options.cssHeadDataCompression compress css head data + * @param {string} options.undoPath undo path for public path auto + * @param {Chunk} options.chunk chunk + * @param {ChunkGraph} options.chunkGraph chunk graph + * @param {CodeGenerationResults} options.codeGenerationResults code generation results + * @param {CssModule[]} options.modules ordered css modules + * @param {RuntimeTemplate} options.runtimeTemplate runtime template + * @param {CompilationHooks} options.hooks hooks + * @returns {Source} generated source + */ + renderChunk({ + uniqueName, + cssHeadDataCompression, + undoPath, + chunk, + chunkGraph, + codeGenerationResults, + modules, + runtimeTemplate, + hooks + }) { + const source = new ConcatSource(); + /** @type {string[]} */ + const metaData = []; + for (const module of modules) { + try { + const moduleSource = this.renderModule({ + metaData, + undoPath, + chunk, + chunkGraph, + codeGenerationResults, + module, + runtimeTemplate, + hooks + }); + source.add(moduleSource); + } catch (err) { + /** @type {Error} */ + (err).message += `\nduring rendering of css ${module.identifier()}`; + throw err; + } + } + const metaDataStr = metaData.join(","); + source.add( + `head{--webpack-${escapeCss( + (uniqueName ? `${uniqueName}-` : "") + chunk.id, + true + )}:${cssHeadDataCompression ? lzwEncode(metaDataStr) : metaDataStr};}` + ); + chunk.rendered = true; + return source; + } + + /** + * @param {Chunk} chunk chunk + * @param {OutputOptions} outputOptions output options + * @returns {TemplatePath} used filename template + */ + static getChunkFilenameTemplate(chunk, outputOptions) { + if (chunk.cssFilenameTemplate) { + return chunk.cssFilenameTemplate; + } else if (chunk.canBeInitial()) { + return /** @type {TemplatePath} */ (outputOptions.cssFilename); + } + return /** @type {TemplatePath} */ (outputOptions.cssChunkFilename); + } + + /** + * @param {Chunk} chunk chunk + * @param {ChunkGraph} chunkGraph chunk graph + * @returns {boolean} true, when the chunk has css + */ + static chunkHasCss(chunk, chunkGraph) { + return ( + Boolean(chunkGraph.getChunkModulesIterableBySourceType(chunk, "css")) || + Boolean( + chunkGraph.getChunkModulesIterableBySourceType(chunk, "css-import") + ) + ); + } +} + +module.exports = CssModulesPlugin; diff --git a/node_modules/webpack/lib/css/CssParser.js b/node_modules/webpack/lib/css/CssParser.js new file mode 100644 index 00000000..cf7633bf --- /dev/null +++ b/node_modules/webpack/lib/css/CssParser.js @@ -0,0 +1,1049 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ModuleDependencyWarning = require("../ModuleDependencyWarning"); +const { CSS_MODULE_TYPE_AUTO } = require("../ModuleTypeConstants"); +const Parser = require("../Parser"); +const WebpackError = require("../WebpackError"); +const ConstDependency = require("../dependencies/ConstDependency"); +const CssExportDependency = require("../dependencies/CssExportDependency"); +const CssImportDependency = require("../dependencies/CssImportDependency"); +const CssLocalIdentifierDependency = require("../dependencies/CssLocalIdentifierDependency"); +const CssSelfLocalIdentifierDependency = require("../dependencies/CssSelfLocalIdentifierDependency"); +const CssUrlDependency = require("../dependencies/CssUrlDependency"); +const StaticExportsDependency = require("../dependencies/StaticExportsDependency"); +const { parseResource } = require("../util/identifier"); +const walkCssTokens = require("./walkCssTokens"); + +/** @typedef {import("../Parser").ParserState} ParserState */ +/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */ +/** @typedef {[number, number]} Range */ + +const CC_LEFT_CURLY = "{".charCodeAt(0); +const CC_RIGHT_CURLY = "}".charCodeAt(0); +const CC_COLON = ":".charCodeAt(0); +const CC_SLASH = "/".charCodeAt(0); +const CC_SEMICOLON = ";".charCodeAt(0); + +// https://www.w3.org/TR/css-syntax-3/#newline +// We don't have `preprocessing` stage, so we need specify all of them +const STRING_MULTILINE = /\\[\n\r\f]/g; +// https://www.w3.org/TR/css-syntax-3/#whitespace +const TRIM_WHITE_SPACES = /(^[ \t\n\r\f]*|[ \t\n\r\f]*$)/g; +const UNESCAPE = /\\([0-9a-fA-F]{1,6}[ \t\n\r\f]?|[\s\S])/g; +const IMAGE_SET_FUNCTION = /^(-\w+-)?image-set$/i; +const OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE = /^@(-\w+-)?keyframes$/; +const OPTIONALLY_VENDOR_PREFIXED_ANIMATION_PROPERTY = + /^(-\w+-)?animation(-name)?$/i; +const IS_MODULES = /\.module(s)?\.[^.]+$/i; + +/** + * @param {string} str url string + * @param {boolean} isString is url wrapped in quotes + * @returns {string} normalized url + */ +const normalizeUrl = (str, isString) => { + // Remove extra spaces and newlines: + // `url("im\ + // g.png")` + if (isString) { + str = str.replace(STRING_MULTILINE, ""); + } + + str = str + // Remove unnecessary spaces from `url(" img.png ")` + .replace(TRIM_WHITE_SPACES, "") + // Unescape + .replace(UNESCAPE, match => { + if (match.length > 2) { + return String.fromCharCode(Number.parseInt(match.slice(1).trim(), 16)); + } + return match[1]; + }); + + if (/^data:/i.test(str)) { + return str; + } + + if (str.includes("%")) { + // Convert `url('%2E/img.png')` -> `url('./img.png')` + try { + str = decodeURIComponent(str); + } catch (_err) { + // Ignore + } + } + + return str; +}; + +class LocConverter { + /** + * @param {string} input input + */ + constructor(input) { + this._input = input; + this.line = 1; + this.column = 0; + this.pos = 0; + } + + /** + * @param {number} pos position + * @returns {LocConverter} location converter + */ + get(pos) { + if (this.pos !== pos) { + if (this.pos < pos) { + const str = this._input.slice(this.pos, pos); + let i = str.lastIndexOf("\n"); + if (i === -1) { + this.column += str.length; + } else { + this.column = str.length - i - 1; + this.line++; + while (i > 0 && (i = str.lastIndexOf("\n", i - 1)) !== -1) + this.line++; + } + } else { + let i = this._input.lastIndexOf("\n", this.pos); + while (i >= pos) { + this.line--; + i = i > 0 ? this._input.lastIndexOf("\n", i - 1) : -1; + } + this.column = pos - i; + } + this.pos = pos; + } + return this; + } +} + +const CSS_MODE_TOP_LEVEL = 0; +const CSS_MODE_IN_BLOCK = 1; +const CSS_MODE_IN_AT_IMPORT = 2; +const CSS_MODE_AT_IMPORT_INVALID = 3; +const CSS_MODE_AT_NAMESPACE_INVALID = 4; + +class CssParser extends Parser { + constructor({ + allowModeSwitch = true, + defaultMode = "global", + namedExports = true + } = {}) { + super(); + this.allowModeSwitch = allowModeSwitch; + this.defaultMode = defaultMode; + this.namedExports = namedExports; + } + + /** + * @param {ParserState} state parser state + * @param {string} message warning message + * @param {LocConverter} locConverter location converter + * @param {number} start start offset + * @param {number} end end offset + */ + _emitWarning(state, message, locConverter, start, end) { + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(end); + + state.current.addWarning( + new ModuleDependencyWarning(state.module, new WebpackError(message), { + start: { line: sl, column: sc }, + end: { line: el, column: ec } + }) + ); + } + + /** + * @param {string | Buffer | PreparsedAst} source the source to parse + * @param {ParserState} state the parser state + * @returns {ParserState} the parser state + */ + parse(source, state) { + if (Buffer.isBuffer(source)) { + source = source.toString("utf-8"); + } else if (typeof source === "object") { + throw new Error("webpackAst is unexpected for the CssParser"); + } + if (source[0] === "\uFEFF") { + source = source.slice(1); + } + + const module = state.module; + + /** @type {string | undefined} */ + let oldDefaultMode; + + if ( + module.type === CSS_MODULE_TYPE_AUTO && + IS_MODULES.test( + parseResource(module.matchResource || module.resource).path + ) + ) { + oldDefaultMode = this.defaultMode; + + this.defaultMode = "local"; + } + + const locConverter = new LocConverter(source); + /** @type {Set<string>} */ + const declaredCssVariables = new Set(); + /** @type {number} */ + let scope = CSS_MODE_TOP_LEVEL; + /** @type {number} */ + let blockNestingLevel = 0; + /** @type {boolean} */ + let allowImportAtRule = true; + /** @type {"local" | "global" | undefined} */ + let modeData; + /** @type {[number, number] | undefined} */ + let lastIdentifier; + /** @type [string, number, number][] */ + const balanced = []; + /** @type {undefined | { start: number, url?: string, urlStart?: number, urlEnd?: number, layer?: string, layerStart?: number, layerEnd?: number, supports?: string, supportsStart?: number, supportsEnd?: number, inSupports?:boolean, media?: string }} */ + let importData; + /** @type {boolean} */ + let inAnimationProperty = false; + /** @type {boolean} */ + let isNextRulePrelude = true; + + /** + * @param {string} input input + * @param {number} pos position + * @returns {boolean} true, when next is nested syntax + */ + const isNextNestedSyntax = (input, pos) => { + pos = walkCssTokens.eatWhitespaceAndComments(input, pos); + + if (input[pos] === "}") { + return false; + } + + // According spec only identifier can be used as a property name + const isIdentifier = walkCssTokens.isIdentStartCodePoint( + input.charCodeAt(pos) + ); + + return !isIdentifier; + }; + /** + * @returns {boolean} true, when in local scope + */ + const isLocalMode = () => + modeData === "local" || + (this.defaultMode === "local" && modeData === undefined); + /** + * @param {string} chars characters + * @returns {(input: string, pos: number) => number} function to eat characters + */ + const eatUntil = chars => { + const charCodes = Array.from({ length: chars.length }, (_, i) => + chars.charCodeAt(i) + ); + const arr = Array.from( + { length: charCodes.reduce((a, b) => Math.max(a, b), 0) + 1 }, + () => false + ); + for (const cc of charCodes) { + arr[cc] = true; + } + return (input, pos) => { + for (;;) { + const cc = input.charCodeAt(pos); + if (cc < arr.length && arr[cc]) { + return pos; + } + pos++; + if (pos === input.length) return pos; + } + }; + }; + /** + * @param {string} input input + * @param {number} pos start position + * @param {(input: string, pos: number) => number} eater eater + * @returns {[number,string]} new position and text + */ + const eatText = (input, pos, eater) => { + let text = ""; + for (;;) { + if (input.charCodeAt(pos) === CC_SLASH) { + const newPos = walkCssTokens.eatComments(input, pos); + if (pos !== newPos) { + pos = newPos; + if (pos === input.length) break; + } else { + text += "/"; + pos++; + if (pos === input.length) break; + } + } + const newPos = eater(input, pos); + if (pos !== newPos) { + text += input.slice(pos, newPos); + pos = newPos; + } else { + break; + } + if (pos === input.length) break; + } + return [pos, text.trimEnd()]; + }; + const eatExportName = eatUntil(":};/"); + const eatExportValue = eatUntil("};/"); + /** + * @param {string} input input + * @param {number} pos start position + * @returns {number} position after parse + */ + const parseExports = (input, pos) => { + pos = walkCssTokens.eatWhitespaceAndComments(input, pos); + const cc = input.charCodeAt(pos); + if (cc !== CC_LEFT_CURLY) { + this._emitWarning( + state, + `Unexpected '${input[pos]}' at ${pos} during parsing of ':export' (expected '{')`, + locConverter, + pos, + pos + ); + return pos; + } + pos++; + pos = walkCssTokens.eatWhitespaceAndComments(input, pos); + for (;;) { + if (input.charCodeAt(pos) === CC_RIGHT_CURLY) break; + pos = walkCssTokens.eatWhitespaceAndComments(input, pos); + if (pos === input.length) return pos; + const start = pos; + let name; + [pos, name] = eatText(input, pos, eatExportName); + if (pos === input.length) return pos; + if (input.charCodeAt(pos) !== CC_COLON) { + this._emitWarning( + state, + `Unexpected '${input[pos]}' at ${pos} during parsing of export name in ':export' (expected ':')`, + locConverter, + start, + pos + ); + return pos; + } + pos++; + if (pos === input.length) return pos; + pos = walkCssTokens.eatWhitespaceAndComments(input, pos); + if (pos === input.length) return pos; + let value; + [pos, value] = eatText(input, pos, eatExportValue); + if (pos === input.length) return pos; + const cc = input.charCodeAt(pos); + if (cc === CC_SEMICOLON) { + pos++; + if (pos === input.length) return pos; + pos = walkCssTokens.eatWhitespaceAndComments(input, pos); + if (pos === input.length) return pos; + } else if (cc !== CC_RIGHT_CURLY) { + this._emitWarning( + state, + `Unexpected '${input[pos]}' at ${pos} during parsing of export value in ':export' (expected ';' or '}')`, + locConverter, + start, + pos + ); + return pos; + } + const dep = new CssExportDependency(name, value); + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(pos); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + } + pos++; + if (pos === input.length) return pos; + pos = walkCssTokens.eatWhiteLine(input, pos); + return pos; + }; + const eatPropertyName = eatUntil(":{};"); + /** + * @param {string} input input + * @param {number} pos name start position + * @param {number} end name end position + * @returns {number} position after handling + */ + const processLocalDeclaration = (input, pos, end) => { + modeData = undefined; + pos = walkCssTokens.eatWhitespaceAndComments(input, pos); + const propertyNameStart = pos; + const [propertyNameEnd, propertyName] = eatText( + input, + pos, + eatPropertyName + ); + if (input.charCodeAt(propertyNameEnd) !== CC_COLON) return end; + pos = propertyNameEnd + 1; + if (propertyName.startsWith("--")) { + // CSS Variable + const { line: sl, column: sc } = locConverter.get(propertyNameStart); + const { line: el, column: ec } = locConverter.get(propertyNameEnd); + const name = propertyName.slice(2); + const dep = new CssLocalIdentifierDependency( + name, + [propertyNameStart, propertyNameEnd], + "--" + ); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + declaredCssVariables.add(name); + } else if ( + !propertyName.startsWith("--") && + OPTIONALLY_VENDOR_PREFIXED_ANIMATION_PROPERTY.test(propertyName) + ) { + inAnimationProperty = true; + } + return pos; + }; + /** + * @param {string} input input + */ + const processDeclarationValueDone = input => { + if (inAnimationProperty && lastIdentifier) { + const { line: sl, column: sc } = locConverter.get(lastIdentifier[0]); + const { line: el, column: ec } = locConverter.get(lastIdentifier[1]); + const name = input.slice(lastIdentifier[0], lastIdentifier[1]); + const dep = new CssSelfLocalIdentifierDependency(name, lastIdentifier); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + lastIdentifier = undefined; + } + }; + const eatKeyframes = eatUntil("{};/"); + const eatNameInVar = eatUntil(",)};/"); + walkCssTokens(source, { + isSelector: () => isNextRulePrelude, + url: (input, start, end, contentStart, contentEnd) => { + const value = normalizeUrl( + input.slice(contentStart, contentEnd), + false + ); + + switch (scope) { + case CSS_MODE_IN_AT_IMPORT: { + // Do not parse URLs in `supports(...)` + if (importData.inSupports) { + break; + } + + if (importData.url) { + this._emitWarning( + state, + `Duplicate of 'url(...)' in '${input.slice( + importData.start, + end + )}'`, + locConverter, + start, + end + ); + + break; + } + + importData.url = value; + importData.urlStart = start; + importData.urlEnd = end; + break; + } + // Do not parse URLs in import between rules + case CSS_MODE_AT_NAMESPACE_INVALID: + case CSS_MODE_AT_IMPORT_INVALID: { + break; + } + case CSS_MODE_IN_BLOCK: { + // Ignore `url()`, `url('')` and `url("")`, they are valid by spec + if (value.length === 0) { + break; + } + + const dep = new CssUrlDependency(value, [start, end], "url"); + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(end); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + module.addCodeGenerationDependency(dep); + break; + } + } + return end; + }, + string: (input, start, end) => { + switch (scope) { + case CSS_MODE_IN_AT_IMPORT: { + const insideURLFunction = + balanced[balanced.length - 1] && + balanced[balanced.length - 1][0] === "url"; + + // Do not parse URLs in `supports(...)` and other strings if we already have a URL + if ( + importData.inSupports || + (!insideURLFunction && importData.url) + ) { + break; + } + + if (insideURLFunction && importData.url) { + this._emitWarning( + state, + `Duplicate of 'url(...)' in '${input.slice( + importData.start, + end + )}'`, + locConverter, + start, + end + ); + + break; + } + + importData.url = normalizeUrl( + input.slice(start + 1, end - 1), + true + ); + + if (!insideURLFunction) { + importData.urlStart = start; + importData.urlEnd = end; + } + + break; + } + case CSS_MODE_IN_BLOCK: { + // TODO move escaped parsing to tokenizer + const last = balanced[balanced.length - 1]; + + if ( + last && + (last[0].replace(/\\/g, "").toLowerCase() === "url" || + IMAGE_SET_FUNCTION.test(last[0].replace(/\\/g, ""))) + ) { + const value = normalizeUrl(input.slice(start + 1, end - 1), true); + + // Ignore `url()`, `url('')` and `url("")`, they are valid by spec + if (value.length === 0) { + break; + } + + const isUrl = last[0].replace(/\\/g, "").toLowerCase() === "url"; + const dep = new CssUrlDependency( + value, + [start, end], + isUrl ? "string" : "url" + ); + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(end); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + module.addCodeGenerationDependency(dep); + } + } + } + return end; + }, + atKeyword: (input, start, end) => { + const name = input.slice(start, end).toLowerCase(); + if (name === "@namespace") { + scope = CSS_MODE_AT_NAMESPACE_INVALID; + this._emitWarning( + state, + "'@namespace' is not supported in bundled CSS", + locConverter, + start, + end + ); + return end; + } else if (name === "@import") { + if (!allowImportAtRule) { + scope = CSS_MODE_AT_IMPORT_INVALID; + this._emitWarning( + state, + "Any '@import' rules must precede all other rules", + locConverter, + start, + end + ); + return end; + } + + scope = CSS_MODE_IN_AT_IMPORT; + importData = { start }; + } else if ( + this.allowModeSwitch && + OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE.test(name) + ) { + let pos = end; + pos = walkCssTokens.eatWhitespaceAndComments(input, pos); + if (pos === input.length) return pos; + const [newPos, name] = eatText(input, pos, eatKeyframes); + if (newPos === input.length) return newPos; + if (input.charCodeAt(newPos) !== CC_LEFT_CURLY) { + this._emitWarning( + state, + `Unexpected '${input[newPos]}' at ${newPos} during parsing of @keyframes (expected '{')`, + locConverter, + start, + end + ); + + return newPos; + } + if (isLocalMode()) { + const { line: sl, column: sc } = locConverter.get(pos); + const { line: el, column: ec } = locConverter.get(newPos); + const dep = new CssLocalIdentifierDependency(name, [pos, newPos]); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + } + pos = newPos; + return pos + 1; + } else if (this.allowModeSwitch && name === "@property") { + let pos = end; + pos = walkCssTokens.eatWhitespaceAndComments(input, pos); + if (pos === input.length) return pos; + const propertyNameStart = pos; + const [propertyNameEnd, propertyName] = eatText( + input, + pos, + eatKeyframes + ); + if (propertyNameEnd === input.length) return propertyNameEnd; + if (!propertyName.startsWith("--")) return propertyNameEnd; + if (input.charCodeAt(propertyNameEnd) !== CC_LEFT_CURLY) { + this._emitWarning( + state, + `Unexpected '${input[propertyNameEnd]}' at ${propertyNameEnd} during parsing of @property (expected '{')`, + locConverter, + start, + end + ); + + return propertyNameEnd; + } + const name = propertyName.slice(2); + declaredCssVariables.add(name); + if (isLocalMode()) { + const { line: sl, column: sc } = locConverter.get(pos); + const { line: el, column: ec } = locConverter.get(propertyNameEnd); + const dep = new CssLocalIdentifierDependency( + name, + [propertyNameStart, propertyNameEnd], + "--" + ); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + } + pos = propertyNameEnd; + return pos + 1; + } else if ( + name === "@media" || + name === "@supports" || + name === "@layer" || + name === "@container" + ) { + modeData = isLocalMode() ? "local" : "global"; + isNextRulePrelude = true; + return end; + } else if (this.allowModeSwitch) { + modeData = "global"; + isNextRulePrelude = false; + } + return end; + }, + semicolon: (input, start, end) => { + switch (scope) { + case CSS_MODE_IN_AT_IMPORT: { + const { start } = importData; + + if (importData.url === undefined) { + this._emitWarning( + state, + `Expected URL in '${input.slice(start, end)}'`, + locConverter, + start, + end + ); + importData = undefined; + scope = CSS_MODE_TOP_LEVEL; + return end; + } + if ( + importData.urlStart > importData.layerStart || + importData.urlStart > importData.supportsStart + ) { + this._emitWarning( + state, + `An URL in '${input.slice( + start, + end + )}' should be before 'layer(...)' or 'supports(...)'`, + locConverter, + start, + end + ); + importData = undefined; + scope = CSS_MODE_TOP_LEVEL; + return end; + } + if (importData.layerStart > importData.supportsStart) { + this._emitWarning( + state, + `The 'layer(...)' in '${input.slice( + start, + end + )}' should be before 'supports(...)'`, + locConverter, + start, + end + ); + importData = undefined; + scope = CSS_MODE_TOP_LEVEL; + return end; + } + + const semicolonPos = end; + end = walkCssTokens.eatWhiteLine(input, end); + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(end); + const lastEnd = + importData.supportsEnd || + importData.layerEnd || + importData.urlEnd || + start; + const pos = walkCssTokens.eatWhitespaceAndComments(input, lastEnd); + // Prevent to consider comments as a part of media query + if (pos !== semicolonPos - 1) { + importData.media = input.slice(lastEnd, semicolonPos - 1).trim(); + } + + const url = importData.url.trim(); + + if (url.length === 0) { + const dep = new ConstDependency("", [start, end]); + module.addPresentationalDependency(dep); + dep.setLoc(sl, sc, el, ec); + } else { + const dep = new CssImportDependency( + url, + [start, end], + importData.layer, + importData.supports, + importData.media && importData.media.length > 0 + ? importData.media + : undefined + ); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + } + + importData = undefined; + scope = CSS_MODE_TOP_LEVEL; + + break; + } + case CSS_MODE_AT_IMPORT_INVALID: + case CSS_MODE_AT_NAMESPACE_INVALID: { + scope = CSS_MODE_TOP_LEVEL; + + break; + } + case CSS_MODE_IN_BLOCK: { + if (this.allowModeSwitch) { + processDeclarationValueDone(input); + inAnimationProperty = false; + isNextRulePrelude = isNextNestedSyntax(input, end); + } + break; + } + } + return end; + }, + leftCurlyBracket: (input, start, end) => { + switch (scope) { + case CSS_MODE_TOP_LEVEL: { + allowImportAtRule = false; + scope = CSS_MODE_IN_BLOCK; + blockNestingLevel = 1; + + if (this.allowModeSwitch) { + isNextRulePrelude = isNextNestedSyntax(input, end); + } + + break; + } + case CSS_MODE_IN_BLOCK: { + blockNestingLevel++; + + if (this.allowModeSwitch) { + isNextRulePrelude = isNextNestedSyntax(input, end); + } + break; + } + } + return end; + }, + rightCurlyBracket: (input, start, end) => { + switch (scope) { + case CSS_MODE_IN_BLOCK: { + if (isLocalMode()) { + processDeclarationValueDone(input); + inAnimationProperty = false; + } + if (--blockNestingLevel === 0) { + scope = CSS_MODE_TOP_LEVEL; + + if (this.allowModeSwitch) { + isNextRulePrelude = true; + modeData = undefined; + } + } else if (this.allowModeSwitch) { + isNextRulePrelude = isNextNestedSyntax(input, end); + } + break; + } + } + return end; + }, + identifier: (input, start, end) => { + switch (scope) { + case CSS_MODE_IN_BLOCK: { + if (isLocalMode()) { + // Handle only top level values and not inside functions + if (inAnimationProperty && balanced.length === 0) { + lastIdentifier = [start, end]; + } else { + return processLocalDeclaration(input, start, end); + } + } + break; + } + case CSS_MODE_IN_AT_IMPORT: { + if (input.slice(start, end).toLowerCase() === "layer") { + importData.layer = ""; + importData.layerStart = start; + importData.layerEnd = end; + } + break; + } + } + return end; + }, + class: (input, start, end) => { + if (isLocalMode()) { + const name = input.slice(start + 1, end); + const dep = new CssLocalIdentifierDependency(name, [start + 1, end]); + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(end); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + } + + return end; + }, + id: (input, start, end) => { + if (isLocalMode()) { + const name = input.slice(start + 1, end); + const dep = new CssLocalIdentifierDependency(name, [start + 1, end]); + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(end); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + } + return end; + }, + function: (input, start, end) => { + let name = input.slice(start, end - 1); + + balanced.push([name, start, end]); + + if ( + scope === CSS_MODE_IN_AT_IMPORT && + name.toLowerCase() === "supports" + ) { + importData.inSupports = true; + } + + if (isLocalMode()) { + name = name.toLowerCase(); + + // Don't rename animation name when we have `var()` function + if (inAnimationProperty && balanced.length === 1) { + lastIdentifier = undefined; + } + + if (name === "var") { + const pos = walkCssTokens.eatWhitespaceAndComments(input, end); + if (pos === input.length) return pos; + const [newPos, name] = eatText(input, pos, eatNameInVar); + if (!name.startsWith("--")) return end; + const { line: sl, column: sc } = locConverter.get(pos); + const { line: el, column: ec } = locConverter.get(newPos); + const dep = new CssSelfLocalIdentifierDependency( + name.slice(2), + [pos, newPos], + "--", + declaredCssVariables + ); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + return newPos; + } + } + + return end; + }, + leftParenthesis: (input, start, end) => { + balanced.push(["(", start, end]); + + return end; + }, + rightParenthesis: (input, start, end) => { + const last = balanced[balanced.length - 1]; + const popped = balanced.pop(); + + if ( + this.allowModeSwitch && + popped && + (popped[0] === ":local" || popped[0] === ":global") + ) { + modeData = balanced[balanced.length - 1] + ? /** @type {"local" | "global"} */ + (balanced[balanced.length - 1][0]) + : undefined; + const dep = new ConstDependency("", [start, end]); + module.addPresentationalDependency(dep); + + return end; + } + + switch (scope) { + case CSS_MODE_IN_AT_IMPORT: { + if (last && last[0] === "url" && !importData.inSupports) { + importData.urlStart = last[1]; + importData.urlEnd = end; + } else if ( + last && + last[0].toLowerCase() === "layer" && + !importData.inSupports + ) { + importData.layer = input.slice(last[2], end - 1).trim(); + importData.layerStart = last[1]; + importData.layerEnd = end; + } else if (last && last[0].toLowerCase() === "supports") { + importData.supports = input.slice(last[2], end - 1).trim(); + importData.supportsStart = last[1]; + importData.supportsEnd = end; + importData.inSupports = false; + } + break; + } + } + + return end; + }, + pseudoClass: (input, start, end) => { + if (this.allowModeSwitch) { + const name = input.slice(start, end).toLowerCase(); + + if (name === ":global") { + modeData = "global"; + // Eat extra whitespace and comments + end = walkCssTokens.eatWhitespace(input, end); + const dep = new ConstDependency("", [start, end]); + module.addPresentationalDependency(dep); + return end; + } else if (name === ":local") { + modeData = "local"; + // Eat extra whitespace and comments + end = walkCssTokens.eatWhitespace(input, end); + const dep = new ConstDependency("", [start, end]); + module.addPresentationalDependency(dep); + return end; + } + + switch (scope) { + case CSS_MODE_TOP_LEVEL: { + if (name === ":export") { + const pos = parseExports(input, end); + const dep = new ConstDependency("", [start, pos]); + module.addPresentationalDependency(dep); + return pos; + } + break; + } + } + } + + return end; + }, + pseudoFunction: (input, start, end) => { + let name = input.slice(start, end - 1); + + balanced.push([name, start, end]); + + if (this.allowModeSwitch) { + name = name.toLowerCase(); + + if (name === ":global") { + modeData = "global"; + const dep = new ConstDependency("", [start, end]); + module.addPresentationalDependency(dep); + } else if (name === ":local") { + modeData = "local"; + const dep = new ConstDependency("", [start, end]); + module.addPresentationalDependency(dep); + } + } + + return end; + }, + comma: (input, start, end) => { + if (this.allowModeSwitch) { + // Reset stack for `:global .class :local .class-other` selector after + modeData = undefined; + + switch (scope) { + case CSS_MODE_IN_BLOCK: { + if (isLocalMode()) { + processDeclarationValueDone(input); + } + + break; + } + } + } + return end; + } + }); + + if (oldDefaultMode) { + this.defaultMode = oldDefaultMode; + } + + module.buildInfo.strict = true; + module.buildMeta.exportsType = this.namedExports ? "namespace" : "default"; + + if (!this.namedExports) { + module.buildMeta.defaultObject = "redirect"; + } + + module.addDependency(new StaticExportsDependency([], true)); + return state; + } +} + +module.exports = CssParser; diff --git a/node_modules/webpack/lib/css/walkCssTokens.js b/node_modules/webpack/lib/css/walkCssTokens.js new file mode 100644 index 00000000..84951538 --- /dev/null +++ b/node_modules/webpack/lib/css/walkCssTokens.js @@ -0,0 +1,775 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** + * @typedef {object} CssTokenCallbacks + * @property {function(string, number): boolean=} isSelector + * @property {function(string, number, number, number, number): number=} url + * @property {function(string, number, number): number=} string + * @property {function(string, number, number): number=} leftParenthesis + * @property {function(string, number, number): number=} rightParenthesis + * @property {function(string, number, number): number=} pseudoFunction + * @property {function(string, number, number): number=} function + * @property {function(string, number, number): number=} pseudoClass + * @property {function(string, number, number): number=} atKeyword + * @property {function(string, number, number): number=} class + * @property {function(string, number, number): number=} identifier + * @property {function(string, number, number): number=} id + * @property {function(string, number, number): number=} leftCurlyBracket + * @property {function(string, number, number): number=} rightCurlyBracket + * @property {function(string, number, number): number=} semicolon + * @property {function(string, number, number): number=} comma + */ + +/** @typedef {function(string, number, CssTokenCallbacks): number} CharHandler */ + +// spec: https://drafts.csswg.org/css-syntax/ + +const CC_LINE_FEED = "\n".charCodeAt(0); +const CC_CARRIAGE_RETURN = "\r".charCodeAt(0); +const CC_FORM_FEED = "\f".charCodeAt(0); + +const CC_TAB = "\t".charCodeAt(0); +const CC_SPACE = " ".charCodeAt(0); + +const CC_SOLIDUS = "/".charCodeAt(0); +const CC_REVERSE_SOLIDUS = "\\".charCodeAt(0); +const CC_ASTERISK = "*".charCodeAt(0); + +const CC_LEFT_PARENTHESIS = "(".charCodeAt(0); +const CC_RIGHT_PARENTHESIS = ")".charCodeAt(0); +const CC_LEFT_CURLY = "{".charCodeAt(0); +const CC_RIGHT_CURLY = "}".charCodeAt(0); +const CC_LEFT_SQUARE = "[".charCodeAt(0); +const CC_RIGHT_SQUARE = "]".charCodeAt(0); + +const CC_QUOTATION_MARK = '"'.charCodeAt(0); +const CC_APOSTROPHE = "'".charCodeAt(0); + +const CC_FULL_STOP = ".".charCodeAt(0); +const CC_COLON = ":".charCodeAt(0); +const CC_SEMICOLON = ";".charCodeAt(0); +const CC_COMMA = ",".charCodeAt(0); +const CC_PERCENTAGE = "%".charCodeAt(0); +const CC_AT_SIGN = "@".charCodeAt(0); + +const CC_LOW_LINE = "_".charCodeAt(0); +const CC_LOWER_A = "a".charCodeAt(0); +const CC_LOWER_U = "u".charCodeAt(0); +const CC_LOWER_E = "e".charCodeAt(0); +const CC_LOWER_Z = "z".charCodeAt(0); +const CC_UPPER_A = "A".charCodeAt(0); +const CC_UPPER_E = "E".charCodeAt(0); +const CC_UPPER_U = "U".charCodeAt(0); +const CC_UPPER_Z = "Z".charCodeAt(0); +const CC_0 = "0".charCodeAt(0); +const CC_9 = "9".charCodeAt(0); + +const CC_NUMBER_SIGN = "#".charCodeAt(0); +const CC_PLUS_SIGN = "+".charCodeAt(0); +const CC_HYPHEN_MINUS = "-".charCodeAt(0); + +const CC_LESS_THAN_SIGN = "<".charCodeAt(0); +const CC_GREATER_THAN_SIGN = ">".charCodeAt(0); + +/** + * @param {number} cc char code + * @returns {boolean} true, if cc is a newline + */ +const _isNewLine = cc => + cc === CC_LINE_FEED || cc === CC_CARRIAGE_RETURN || cc === CC_FORM_FEED; + +/** @type {CharHandler} */ +const consumeSpace = (input, pos, _callbacks) => { + /** @type {number} */ + let cc; + do { + pos++; + cc = input.charCodeAt(pos); + } while (_isWhiteSpace(cc)); + return pos; +}; + +/** + * @param {number} cc char code + * @returns {boolean} true, if cc is a newline + */ +const _isNewline = cc => + cc === CC_LINE_FEED || cc === CC_CARRIAGE_RETURN || cc === CC_FORM_FEED; + +/** + * @param {number} cc char code + * @returns {boolean} true, if cc is a space (U+0009 CHARACTER TABULATION or U+0020 SPACE) + */ +const _isSpace = cc => cc === CC_TAB || cc === CC_SPACE; + +/** + * @param {number} cc char code + * @returns {boolean} true, if cc is a whitespace + */ +const _isWhiteSpace = cc => _isNewline(cc) || _isSpace(cc); + +/** + * ident-start code point + * + * A letter, a non-ASCII code point, or U+005F LOW LINE (_). + * @param {number} cc char code + * @returns {boolean} true, if cc is a start code point of an identifier + */ +const isIdentStartCodePoint = cc => + (cc >= CC_LOWER_A && cc <= CC_LOWER_Z) || + (cc >= CC_UPPER_A && cc <= CC_UPPER_Z) || + cc === CC_LOW_LINE || + cc >= 0x80; + +/** @type {CharHandler} */ +const consumeDelimToken = (input, pos, _callbacks) => pos + 1; + +/** @type {CharHandler} */ +const consumeComments = (input, pos, _callbacks) => { + // If the next two input code point are U+002F SOLIDUS (/) followed by a U+002A + // ASTERISK (*), consume them and all following code points up to and including + // the first U+002A ASTERISK (*) followed by a U+002F SOLIDUS (/), or up to an + // EOF code point. Return to the start of this step. + // + // If the preceding paragraph ended by consuming an EOF code point, this is a parse error. + // But we are silent on errors. + if ( + input.charCodeAt(pos) === CC_SOLIDUS && + input.charCodeAt(pos + 1) === CC_ASTERISK + ) { + pos += 1; + while (pos < input.length) { + if ( + input.charCodeAt(pos) === CC_ASTERISK && + input.charCodeAt(pos + 1) === CC_SOLIDUS + ) { + pos += 2; + break; + } + pos++; + } + } + return pos; +}; + +/** @type {function(number): CharHandler} */ +const consumeString = quoteCc => (input, pos, callbacks) => { + const start = pos; + pos = _consumeString(input, pos, quoteCc); + if (callbacks.string !== undefined) { + pos = callbacks.string(input, start, pos); + } + return pos; +}; + +/** + * @param {string} input input + * @param {number} pos position + * @param {number} quoteCc quote char code + * @returns {number} new position + */ +const _consumeString = (input, pos, quoteCc) => { + pos++; + for (;;) { + if (pos === input.length) return pos; + const cc = input.charCodeAt(pos); + if (cc === quoteCc) return pos + 1; + if (_isNewLine(cc)) { + // bad string + return pos; + } + if (cc === CC_REVERSE_SOLIDUS) { + // we don't need to fully parse the escaped code point + // just skip over a potential new line + pos++; + if (pos === input.length) return pos; + pos++; + } else { + pos++; + } + } +}; + +/** + * @param {number} cc char code + * @returns {boolean} is identifier start code + */ +const _isIdentifierStartCode = cc => + cc === CC_LOW_LINE || + (cc >= CC_LOWER_A && cc <= CC_LOWER_Z) || + (cc >= CC_UPPER_A && cc <= CC_UPPER_Z) || + cc > 0x80; + +/** + * @param {number} first first code point + * @param {number} second second code point + * @returns {boolean} true if two code points are a valid escape + */ +const _isTwoCodePointsAreValidEscape = (first, second) => { + if (first !== CC_REVERSE_SOLIDUS) return false; + if (_isNewLine(second)) return false; + return true; +}; + +/** + * @param {number} cc char code + * @returns {boolean} is digit + */ +const _isDigit = cc => cc >= CC_0 && cc <= CC_9; + +/** + * @param {string} input input + * @param {number} pos position + * @returns {boolean} true, if input at pos starts an identifier + */ +const _startsIdentifier = (input, pos) => { + const cc = input.charCodeAt(pos); + if (cc === CC_HYPHEN_MINUS) { + if (pos === input.length) return false; + const cc = input.charCodeAt(pos + 1); + if (cc === CC_HYPHEN_MINUS) return true; + if (cc === CC_REVERSE_SOLIDUS) { + const cc = input.charCodeAt(pos + 2); + return !_isNewLine(cc); + } + return _isIdentifierStartCode(cc); + } + if (cc === CC_REVERSE_SOLIDUS) { + const cc = input.charCodeAt(pos + 1); + return !_isNewLine(cc); + } + return _isIdentifierStartCode(cc); +}; + +/** @type {CharHandler} */ +const consumeNumberSign = (input, pos, callbacks) => { + const start = pos; + pos++; + if (pos === input.length) return pos; + if ( + callbacks.isSelector && + callbacks.isSelector(input, pos) && + _startsIdentifier(input, pos) + ) { + pos = _consumeIdentifier(input, pos, callbacks); + if (callbacks.id !== undefined) { + return callbacks.id(input, start, pos); + } + } + return pos; +}; + +/** @type {CharHandler} */ +const consumeMinus = (input, pos, callbacks) => { + const start = pos; + pos++; + if (pos === input.length) return pos; + const cc = input.charCodeAt(pos); + // If the input stream starts with a number, reconsume the current input code point, consume a numeric token, and return it. + if (cc === CC_FULL_STOP || _isDigit(cc)) { + return consumeNumericToken(input, pos, callbacks); + } else if (cc === CC_HYPHEN_MINUS) { + pos++; + if (pos === input.length) return pos; + const cc = input.charCodeAt(pos); + if (cc === CC_GREATER_THAN_SIGN) { + return pos + 1; + } + pos = _consumeIdentifier(input, pos, callbacks); + if (callbacks.identifier !== undefined) { + return callbacks.identifier(input, start, pos); + } + } else if (cc === CC_REVERSE_SOLIDUS) { + if (pos + 1 === input.length) return pos; + const cc = input.charCodeAt(pos + 1); + if (_isNewLine(cc)) return pos; + pos = _consumeIdentifier(input, pos, callbacks); + if (callbacks.identifier !== undefined) { + return callbacks.identifier(input, start, pos); + } + } else if (_isIdentifierStartCode(cc)) { + pos = consumeOtherIdentifier(input, pos - 1, callbacks); + } + return pos; +}; + +/** @type {CharHandler} */ +const consumeDot = (input, pos, callbacks) => { + const start = pos; + pos++; + if (pos === input.length) return pos; + const cc = input.charCodeAt(pos); + if (_isDigit(cc)) return consumeNumericToken(input, pos - 2, callbacks); + if ( + (callbacks.isSelector && !callbacks.isSelector(input, pos)) || + !_startsIdentifier(input, pos) + ) + return pos; + pos = _consumeIdentifier(input, pos, callbacks); + if (callbacks.class !== undefined) return callbacks.class(input, start, pos); + return pos; +}; + +/** @type {CharHandler} */ +const consumeNumericToken = (input, pos, callbacks) => { + pos = _consumeNumber(input, pos, callbacks); + if (pos === input.length) return pos; + if (_startsIdentifier(input, pos)) + return _consumeIdentifier(input, pos, callbacks); + const cc = input.charCodeAt(pos); + if (cc === CC_PERCENTAGE) return pos + 1; + return pos; +}; + +/** @type {CharHandler} */ +const consumeOtherIdentifier = (input, pos, callbacks) => { + const start = pos; + pos = _consumeIdentifier(input, pos, callbacks); + if (pos !== input.length && input.charCodeAt(pos) === CC_LEFT_PARENTHESIS) { + pos++; + if (callbacks.function !== undefined) { + return callbacks.function(input, start, pos); + } + } else if (callbacks.identifier !== undefined) { + return callbacks.identifier(input, start, pos); + } + return pos; +}; + +/** @type {CharHandler} */ +const consumePotentialUrl = (input, pos, callbacks) => { + const start = pos; + pos = _consumeIdentifier(input, pos, callbacks); + const nextPos = pos + 1; + if ( + pos === start + 3 && + input.slice(start, nextPos).toLowerCase() === "url(" + ) { + pos++; + let cc = input.charCodeAt(pos); + while (_isWhiteSpace(cc)) { + pos++; + if (pos === input.length) return pos; + cc = input.charCodeAt(pos); + } + if (cc === CC_QUOTATION_MARK || cc === CC_APOSTROPHE) { + if (callbacks.function !== undefined) { + return callbacks.function(input, start, nextPos); + } + return nextPos; + } + const contentStart = pos; + /** @type {number} */ + let contentEnd; + for (;;) { + if (cc === CC_REVERSE_SOLIDUS) { + pos++; + if (pos === input.length) return pos; + pos++; + } else if (_isWhiteSpace(cc)) { + contentEnd = pos; + do { + pos++; + if (pos === input.length) return pos; + cc = input.charCodeAt(pos); + } while (_isWhiteSpace(cc)); + if (cc !== CC_RIGHT_PARENTHESIS) return pos; + pos++; + if (callbacks.url !== undefined) { + return callbacks.url(input, start, pos, contentStart, contentEnd); + } + return pos; + } else if (cc === CC_RIGHT_PARENTHESIS) { + contentEnd = pos; + pos++; + if (callbacks.url !== undefined) { + return callbacks.url(input, start, pos, contentStart, contentEnd); + } + return pos; + } else if (cc === CC_LEFT_PARENTHESIS) { + return pos; + } else { + pos++; + } + if (pos === input.length) return pos; + cc = input.charCodeAt(pos); + } + } else { + if (callbacks.identifier !== undefined) { + return callbacks.identifier(input, start, pos); + } + return pos; + } +}; + +/** @type {CharHandler} */ +const consumePotentialPseudo = (input, pos, callbacks) => { + const start = pos; + pos++; + if ( + (callbacks.isSelector && !callbacks.isSelector(input, pos)) || + !_startsIdentifier(input, pos) + ) + return pos; + pos = _consumeIdentifier(input, pos, callbacks); + const cc = input.charCodeAt(pos); + if (cc === CC_LEFT_PARENTHESIS) { + pos++; + if (callbacks.pseudoFunction !== undefined) { + return callbacks.pseudoFunction(input, start, pos); + } + return pos; + } + if (callbacks.pseudoClass !== undefined) { + return callbacks.pseudoClass(input, start, pos); + } + return pos; +}; + +/** @type {CharHandler} */ +const consumeLeftParenthesis = (input, pos, callbacks) => { + pos++; + if (callbacks.leftParenthesis !== undefined) { + return callbacks.leftParenthesis(input, pos - 1, pos); + } + return pos; +}; + +/** @type {CharHandler} */ +const consumeRightParenthesis = (input, pos, callbacks) => { + pos++; + if (callbacks.rightParenthesis !== undefined) { + return callbacks.rightParenthesis(input, pos - 1, pos); + } + return pos; +}; + +/** @type {CharHandler} */ +const consumeLeftCurlyBracket = (input, pos, callbacks) => { + pos++; + if (callbacks.leftCurlyBracket !== undefined) { + return callbacks.leftCurlyBracket(input, pos - 1, pos); + } + return pos; +}; + +/** @type {CharHandler} */ +const consumeRightCurlyBracket = (input, pos, callbacks) => { + pos++; + if (callbacks.rightCurlyBracket !== undefined) { + return callbacks.rightCurlyBracket(input, pos - 1, pos); + } + return pos; +}; + +/** @type {CharHandler} */ +const consumeSemicolon = (input, pos, callbacks) => { + pos++; + if (callbacks.semicolon !== undefined) { + return callbacks.semicolon(input, pos - 1, pos); + } + return pos; +}; + +/** @type {CharHandler} */ +const consumeComma = (input, pos, callbacks) => { + pos++; + if (callbacks.comma !== undefined) { + return callbacks.comma(input, pos - 1, pos); + } + return pos; +}; + +/** @type {CharHandler} */ +const _consumeIdentifier = (input, pos) => { + for (;;) { + const cc = input.charCodeAt(pos); + if (cc === CC_REVERSE_SOLIDUS) { + pos++; + if (pos === input.length) return pos; + pos++; + } else if ( + _isIdentifierStartCode(cc) || + _isDigit(cc) || + cc === CC_HYPHEN_MINUS + ) { + pos++; + } else { + return pos; + } + } +}; + +/** @type {CharHandler} */ +const _consumeNumber = (input, pos) => { + pos++; + if (pos === input.length) return pos; + let cc = input.charCodeAt(pos); + while (_isDigit(cc)) { + pos++; + if (pos === input.length) return pos; + cc = input.charCodeAt(pos); + } + if (cc === CC_FULL_STOP && pos + 1 !== input.length) { + const next = input.charCodeAt(pos + 1); + if (_isDigit(next)) { + pos += 2; + cc = input.charCodeAt(pos); + while (_isDigit(cc)) { + pos++; + if (pos === input.length) return pos; + cc = input.charCodeAt(pos); + } + } + } + if (cc === CC_LOWER_E || cc === CC_UPPER_E) { + if (pos + 1 !== input.length) { + const next = input.charCodeAt(pos + 2); + if (_isDigit(next)) { + pos += 2; + } else if ( + (next === CC_HYPHEN_MINUS || next === CC_PLUS_SIGN) && + pos + 2 !== input.length + ) { + const next = input.charCodeAt(pos + 2); + if (_isDigit(next)) { + pos += 3; + } else { + return pos; + } + } else { + return pos; + } + } + } else { + return pos; + } + cc = input.charCodeAt(pos); + while (_isDigit(cc)) { + pos++; + if (pos === input.length) return pos; + cc = input.charCodeAt(pos); + } + return pos; +}; + +/** @type {CharHandler} */ +const consumeLessThan = (input, pos, _callbacks) => { + if (input.slice(pos + 1, pos + 4) === "!--") return pos + 4; + return pos + 1; +}; + +/** @type {CharHandler} */ +const consumeAt = (input, pos, callbacks) => { + const start = pos; + pos++; + if (pos === input.length) return pos; + if (_startsIdentifier(input, pos)) { + pos = _consumeIdentifier(input, pos, callbacks); + if (callbacks.atKeyword !== undefined) { + pos = callbacks.atKeyword(input, start, pos); + } + } + return pos; +}; + +/** @type {CharHandler} */ +const consumeReverseSolidus = (input, pos, callbacks) => { + const start = pos; + pos++; + if (pos === input.length) return pos; + // If the input stream starts with a valid escape, reconsume the current input code point, consume an ident-like token, and return it. + if ( + _isTwoCodePointsAreValidEscape( + input.charCodeAt(start), + input.charCodeAt(pos) + ) + ) { + return consumeOtherIdentifier(input, pos - 1, callbacks); + } + // Otherwise, this is a parse error. Return a <delim-token> with its value set to the current input code point. + return pos; +}; + +const CHAR_MAP = Array.from({ length: 0x80 }, (_, cc) => { + // https://drafts.csswg.org/css-syntax/#consume-token + switch (cc) { + // whitespace + case CC_LINE_FEED: + case CC_CARRIAGE_RETURN: + case CC_FORM_FEED: + case CC_TAB: + case CC_SPACE: + return consumeSpace; + // U+0022 QUOTATION MARK (") + case CC_QUOTATION_MARK: + return consumeString(cc); + // U+0023 NUMBER SIGN (#) + case CC_NUMBER_SIGN: + return consumeNumberSign; + // U+0027 APOSTROPHE (') + case CC_APOSTROPHE: + return consumeString(cc); + // U+0028 LEFT PARENTHESIS (() + case CC_LEFT_PARENTHESIS: + return consumeLeftParenthesis; + // U+0029 RIGHT PARENTHESIS ()) + case CC_RIGHT_PARENTHESIS: + return consumeRightParenthesis; + // U+002B PLUS SIGN (+) + case CC_PLUS_SIGN: + return consumeNumericToken; + // U+002C COMMA (,) + case CC_COMMA: + return consumeComma; + // U+002D HYPHEN-MINUS (-) + case CC_HYPHEN_MINUS: + return consumeMinus; + // U+002E FULL STOP (.) + case CC_FULL_STOP: + return consumeDot; + // U+003A COLON (:) + case CC_COLON: + return consumePotentialPseudo; + // U+003B SEMICOLON (;) + case CC_SEMICOLON: + return consumeSemicolon; + // U+003C LESS-THAN SIGN (<) + case CC_LESS_THAN_SIGN: + return consumeLessThan; + // U+0040 COMMERCIAL AT (@) + case CC_AT_SIGN: + return consumeAt; + // U+005B LEFT SQUARE BRACKET ([) + case CC_LEFT_SQUARE: + return consumeDelimToken; + // U+005C REVERSE SOLIDUS (\) + case CC_REVERSE_SOLIDUS: + return consumeReverseSolidus; + // U+005D RIGHT SQUARE BRACKET (]) + case CC_RIGHT_SQUARE: + return consumeDelimToken; + // U+007B LEFT CURLY BRACKET ({) + case CC_LEFT_CURLY: + return consumeLeftCurlyBracket; + // U+007D RIGHT CURLY BRACKET (}) + case CC_RIGHT_CURLY: + return consumeRightCurlyBracket; + // Optimization + case CC_LOWER_U: + case CC_UPPER_U: + return consumePotentialUrl; + default: + // digit + if (_isDigit(cc)) return consumeNumericToken; + // ident-start code point + if (isIdentStartCodePoint(cc)) { + return consumeOtherIdentifier; + } + // EOF, but we don't have it + // anything else + return consumeDelimToken; + } +}); + +/** + * @param {string} input input css + * @param {CssTokenCallbacks} callbacks callbacks + * @returns {void} + */ +module.exports = (input, callbacks) => { + // This section describes how to consume a token from a stream of code points. It will return a single token of any type. + let pos = 0; + while (pos < input.length) { + // Consume comments. + pos = consumeComments(input, pos, callbacks); + + const cc = input.charCodeAt(pos); + + // Consume the next input code point. + if (cc < 0x80) { + pos = CHAR_MAP[cc](input, pos, callbacks); + } else { + pos++; + } + } +}; + +module.exports.isIdentStartCodePoint = isIdentStartCodePoint; + +/** + * @param {string} input input + * @param {number} pos position + * @returns {number} position after comments + */ +module.exports.eatComments = (input, pos) => { + for (;;) { + const originalPos = pos; + pos = consumeComments(input, pos, {}); + if (originalPos === pos) { + break; + } + } + + return pos; +}; + +/** + * @param {string} input input + * @param {number} pos position + * @returns {number} position after whitespace + */ +module.exports.eatWhitespace = (input, pos) => { + while (_isWhiteSpace(input.charCodeAt(pos))) { + pos++; + } + + return pos; +}; + +/** + * @param {string} input input + * @param {number} pos position + * @returns {number} position after whitespace and comments + */ +module.exports.eatWhitespaceAndComments = (input, pos) => { + for (;;) { + const originalPos = pos; + pos = consumeComments(input, pos, {}); + while (_isWhiteSpace(input.charCodeAt(pos))) { + pos++; + } + if (originalPos === pos) { + break; + } + } + + return pos; +}; + +/** + * @param {string} input input + * @param {number} pos position + * @returns {number} position after whitespace + */ +module.exports.eatWhiteLine = (input, pos) => { + for (;;) { + const cc = input.charCodeAt(pos); + if (_isSpace(cc)) { + pos++; + continue; + } + if (_isNewLine(cc)) pos++; + // For `\r\n` + if (cc === CC_CARRIAGE_RETURN && input.charCodeAt(pos + 1) === CC_LINE_FEED) + pos++; + break; + } + + return pos; +}; |
