html-bundler-webpack-plugin
Advanced tools
Comparing version 1.3.1 to 1.4.0
# Change log | ||
## 1.4.0 (2023-02-26) | ||
- feat: display watch files in watch/serv mode when verbose option is enabled | ||
- feat: add `auto` value for the `verbose` option | ||
- refactor: improve the code structure | ||
- test: add tests for watch mode | ||
- chore: add GitHub test badge | ||
- docs: improve readme | ||
## 1.3.1 (2023-02-24) | ||
@@ -4,0 +12,0 @@ - fix: after an error, restore watching without restarting |
{ | ||
"name": "html-bundler-webpack-plugin", | ||
"version": "1.3.1", | ||
"version": "1.4.0", | ||
"description": "HTML bundler plugin for webpack handels HTML template as entry point, extracts CSS and JS from their sources specified in HTML.", | ||
@@ -8,3 +8,16 @@ "keywords": [ | ||
"plugin", | ||
"loader", | ||
"bundler", | ||
"bundle", | ||
"extract", | ||
"inline", | ||
"html", | ||
"template", | ||
"ejs", | ||
"handlebars", | ||
"mustache", | ||
"nunjucks", | ||
"liquidjs", | ||
"eta", | ||
"hbs", | ||
"scss", | ||
@@ -15,11 +28,3 @@ "css", | ||
"script", | ||
"inline", | ||
"svg", | ||
"template", | ||
"eta", | ||
"ejs", | ||
"handlebars", | ||
"mustache", | ||
"nunjucks", | ||
"loader" | ||
"svg" | ||
], | ||
@@ -26,0 +31,0 @@ "license": "ISC", |
@@ -5,3 +5,2 @@ const path = require('path'); | ||
const isWin = path.sep === '\\'; | ||
const workingDir = process.env.PWD; | ||
@@ -28,2 +27,20 @@ /** | ||
/** | ||
* Return the path of file relative to a directory. | ||
* | ||
* @param {string} file | ||
* @param {string} dir | ||
* @return {string} | ||
*/ | ||
const pathRelativeByPwd = (file, dir = process.cwd()) => { | ||
let relPath = file; | ||
if (file.startsWith(dir)) { | ||
relPath = path.relative(dir, file); | ||
if (!path.extname(file)) relPath = path.join(relPath, path.sep); | ||
} | ||
return isWin ? pathToPosix(relPath) : relPath; | ||
}; | ||
/** | ||
* Parse the url query. | ||
@@ -83,3 +100,3 @@ * | ||
outToConsole, | ||
workingDir, | ||
pathRelativeByPwd, | ||
}; |
const { minify } = require('html-minifier-terser'); | ||
const Options = require('./Plugin/Options'); | ||
const AssetCompiler = require('./Plugin/AssetCompiler'); | ||
@@ -7,20 +8,4 @@ const loader = require.resolve('./Loader'); | ||
/** | ||
* @typedef {Object} PluginOptions | ||
* @property {RegExp} [test = /\.(html)$/] The search for a match of entry files. | ||
* @property {boolean} [enabled = true] Enable/disable the plugin. | ||
* @property {boolean} [verbose = false] Show the information at processing entry files. | ||
* @typedef {PluginOptions} HtmlBundlerPluginOptions | ||
* @property {boolean|Object|'auto'|null} [minify = false] Minify generated HTML. | ||
* @property {string|null} [sourcePath = options.context] The absolute path to sources. | ||
* @property {string|null} [outputPath = options.output.path] The output directory for an asset. | ||
* @property {string|function(PathData, AssetInfo): string} [filename = '[name].html'] The file name of output file. | ||
* See https://webpack.js.org/configuration/output/#outputfilename. | ||
* Must be an absolute or a relative by the context path. | ||
* @property {function(string, ResourceInfo, Compilation): string|null} postprocess The post process for extracted content from entry. | ||
* @property {Array<ModuleOptions>} [modules = []] | ||
* @property {ModuleOptions|{}} css The options for embedded plugin module to extract CSS. | ||
* @property {ExtractJsOptions|{}} js The options for embedded plugin module to extract CSS. | ||
* @property {boolean} [`extractComments` = false] Whether comments shall be extracted to a separate file. | ||
* If the original filename is foo.js, then the comments will be stored to foo.js.LICENSE.txt. | ||
* This option enable/disable storing of *.LICENSE.txt file. | ||
* For more flexibility use terser-webpack-plugin https://webpack.js.org/plugins/terser-webpack-plugin/#extractcomments. | ||
*/ | ||
@@ -30,7 +15,7 @@ | ||
/** | ||
* @param {PluginOptions|{}} options | ||
* @param {HtmlBundlerPluginOptions|{}} options | ||
*/ | ||
constructor(options = {}) { | ||
const PluginOptions = { | ||
test: /\.(html|ejs)$/, | ||
test: /\.(html|ejs|eta)$/, | ||
enabled: true, | ||
@@ -67,3 +52,3 @@ verbose: false, | ||
const defaultLoader = { | ||
test: /\.(html|ejs|eta)$/, | ||
test: Options.get().test, | ||
loader, | ||
@@ -92,8 +77,8 @@ }; | ||
afterRenderModules(compilation) { | ||
const options = this.options; | ||
const { compiler, assets } = compilation; | ||
const { mode } = compiler.options; | ||
const { RawSource } = compiler.webpack.sources; | ||
const isProductionMode = mode == null || mode === 'production'; | ||
const promises = []; | ||
const options = Options.get(); | ||
let isMinify = Options.isTrue(options.minify, false); | ||
let minifyOptions; | ||
@@ -114,6 +99,6 @@ | ||
if (options.minify === true || (isProductionMode && options.minify === 'auto')) { | ||
if (isMinify) { | ||
minifyOptions = defaultMinifyOptions; | ||
} else if (typeof options.minify === 'object' && options.minify !== null) { | ||
minifyOptions = { ...defaultMinifyOptions, ...this.options.minify }; | ||
} else if (options.minify !== null && typeof options.minify === 'object') { | ||
minifyOptions = { ...defaultMinifyOptions, ...options.minify }; | ||
} else { | ||
@@ -120,0 +105,0 @@ return; |
@@ -5,3 +5,5 @@ const path = require('path'); | ||
const { isWin } = require('./Utils'); | ||
const { verboseWatchFiles } = require('./Messages/Info'); | ||
const { watchPathsException } = require('./Messages/Exeptions'); | ||
const { outToConsole } = require('../Common/Helpers'); | ||
@@ -66,2 +68,6 @@ /** | ||
files.forEach(loaderContext.addDependency); | ||
if (PluginService.getOptions().isVerbose()) { | ||
verboseWatchFiles([...this.files]); | ||
} | ||
} | ||
@@ -68,0 +74,0 @@ |
const Eta = require('eta'); | ||
const PluginService = require('../Plugin/PluginService'); | ||
const { outToConsole } = require('../Common/Helpers'); | ||
@@ -41,3 +42,2 @@ /** | ||
const watchFilesOption = PluginService.getOptions().watchFiles; | ||
const watchFiles = { | ||
@@ -55,3 +55,3 @@ // watch files only in the directories, defaults is the project directory | ||
/[\\/]\..+$/, // hidden dirs and files: .git, .idea, .gitignore, etc. | ||
/package(?:-lock)*.json$/, | ||
/package(?:-lock)*\.json$/, | ||
/webpack\.(.+)\.js$/, | ||
@@ -62,33 +62,31 @@ /\.(je?pg|png|ico|webp|svg|woff2?|ttf|otf|eot)$/, | ||
if (watchFilesOption) { | ||
const { paths, files, ignore } = watchFilesOption; | ||
const { paths, files, ignore } = PluginService.getOptions().getWatchFiles(); | ||
if (paths) { | ||
const entries = Array.isArray(paths) ? paths : [paths]; | ||
for (let item of entries) { | ||
watchFiles.paths.push(item); | ||
} | ||
if (paths) { | ||
const entries = Array.isArray(paths) ? paths : [paths]; | ||
for (let item of entries) { | ||
watchFiles.paths.push(item); | ||
} | ||
if (watchFiles.paths.length === 0) { | ||
watchFiles.paths.push(this.rootContext); | ||
} | ||
} | ||
if (watchFiles.paths.length < 1) { | ||
watchFiles.paths.push(this.rootContext); | ||
} | ||
if (files) { | ||
const entries = Array.isArray(files) ? files : [files]; | ||
for (let item of entries) { | ||
if (item.constructor.name !== 'RegExp') { | ||
item = new RegExp(item); | ||
} | ||
watchFiles.files.push(item); | ||
if (files) { | ||
const entries = Array.isArray(files) ? files : [files]; | ||
for (let item of entries) { | ||
if (item.constructor.name !== 'RegExp') { | ||
item = new RegExp(item); | ||
} | ||
watchFiles.files.push(item); | ||
} | ||
} | ||
if (ignore) { | ||
const entries = Array.isArray(ignore) ? ignore : [ignore]; | ||
for (let item of entries) { | ||
if (item.constructor.name !== 'RegExp') { | ||
item = new RegExp(item); | ||
} | ||
watchFiles.ignore.push(item); | ||
if (ignore) { | ||
const entries = Array.isArray(ignore) ? ignore : [ignore]; | ||
for (let item of entries) { | ||
if (item.constructor.name !== 'RegExp') { | ||
item = new RegExp(item); | ||
} | ||
watchFiles.ignore.push(item); | ||
} | ||
@@ -95,0 +93,0 @@ } |
@@ -53,3 +53,3 @@ const fs = require('fs'); | ||
extensions: ['.scss', '.sass', '.css'], | ||
restrictions: PluginService.getStyleRestrictions(), | ||
restrictions: this.getStyleResolveRestrictions(), | ||
}); | ||
@@ -59,2 +59,13 @@ } | ||
/** | ||
* Return a list of resolve restrictions to restrict the paths that a request can be resolved on. | ||
* @see https://webpack.js.org/configuration/resolve/#resolverestrictions | ||
* @return {Array<RegExp|string>} | ||
*/ | ||
static getStyleResolveRestrictions() { | ||
const restrictions = PluginService.getOptions().getCss().test; | ||
return Array.isArray(restrictions) ? restrictions : [restrictions]; | ||
} | ||
/** | ||
* Resolve filename. | ||
@@ -61,0 +72,0 @@ * |
@@ -11,5 +11,5 @@ const vm = require('vm'); | ||
const Options = require('./Options'); | ||
const PluginService = require('./PluginService'); | ||
const ScriptCollection = require('./ScriptCollection'); | ||
const extractCss = require('./Modules/extractCss'); | ||
@@ -35,7 +35,3 @@ const Resolver = require('./Resolver'); | ||
const { | ||
optionModulesException, | ||
executeTemplateFunctionException, | ||
postprocessException, | ||
} = require('./Messages/Exception'); | ||
const { executeTemplateFunctionException, postprocessException } = require('./Messages/Exception'); | ||
@@ -57,3 +53,3 @@ /** @typedef {import('webpack').Compiler} Compiler */ | ||
* @property {boolean} [enabled = true] Enable/disable the plugin. | ||
* @property {boolean} [verbose = false] Show the information at processing entry files. | ||
* @property {boolean|string} [verbose = false] Show the information at processing entry files. | ||
* @property {string} [sourcePath = options.context] The absolute path to sources. | ||
@@ -67,8 +63,2 @@ * @property {string} [outputPath = options.output.path] The output directory for an asset. | ||
/** | ||
* @typedef {Object} ExtractJsOptions | ||
* @property {boolean} [verbose = false] Show the information at processing entry files. | ||
* @property {string|function(PathData, AssetInfo): string} [filename = '[name].js'] The file name of output file. | ||
*/ | ||
/** | ||
* @typedef {ModuleOptions} ModuleProps | ||
@@ -79,22 +69,5 @@ * @property {boolean} [`inline` = false] Whether inline CSS should contain inline source map. | ||
/** | ||
* @typedef {Object} AssetEntryOptions | ||
* @property {string} name The key of webpack entry. | ||
* @property {string} file The output asset file with absolute path. | ||
* @property {string} assetFile The output asset file with relative path by webpack output path. | ||
* Note: the method compilation.emitAsset() use this file as key of assets object | ||
* and save the file relative by output path, defined in webpack.options.output.path. | ||
* @property {string|function(PathData, AssetInfo): string} filenameTemplate The filename template or function. | ||
* @property {string} filename The asset filename. | ||
* The template strings support only these substitutions: [name], [base], [path], [ext], [id], [contenthash], [contenthash:nn] | ||
* See https://webpack.js.org/configuration/output/#outputfilename | ||
* @property {string} request The full path of import file with query. | ||
* @property {string} importFile The import file only w/o query. | ||
* @property {string} outputPath | ||
* @property {string} sourcePath | ||
* @property {{name: string, type: string}} library Define the output a js file. | ||
* See https://webpack.js.org/configuration/output/#outputlibrary | ||
* @property {function(string, AssetInfo, Compilation): string} [postprocess = null] The post process for extracted content from entry. | ||
* @property {function(): string|null =} extract | ||
* @property {Array} resources | ||
* @property {boolean} [verbose = false] Show an information by handles of the entry in a postprocess. | ||
* @typedef {Object} ExtractJsOptions | ||
* @property {boolean|string} [verbose = false] Show the information at processing entry files. | ||
* @property {string|function(PathData, AssetInfo): string} [filename = '[name].js'] The file name of output file. | ||
*/ | ||
@@ -118,5 +91,2 @@ | ||
class AssetCompiler { | ||
/** @type {PluginOptions} */ | ||
options = {}; | ||
entryLibrary = { | ||
@@ -127,12 +97,3 @@ name: 'return', | ||
// the id to bind loader with data passed into template via entry.data | ||
entryDataIndex = 1; | ||
entryDataCache = new Map(); | ||
// webpack's options and modules | ||
webpackContext = ''; | ||
webpackOutputPath = ''; | ||
// current entry point during dependency compilation | ||
/** @type AssetEntryOptions */ | ||
/** @type AssetEntryOptions The current entry point during dependency compilation. */ | ||
currentEntryPoint; | ||
@@ -144,26 +105,6 @@ | ||
constructor(options = {}) { | ||
this.options = options; | ||
this.enabled = this.options.enabled !== false; | ||
this.verbose = this.options.verbose === true; | ||
Options.init(options); | ||
if (options.modules && !Array.isArray(options.modules)) { | ||
optionModulesException(options.modules); | ||
} | ||
let extractCssOptions = extractCss(options.css); | ||
const moduleExtractCssOptions = this.options.modules.find( | ||
(item) => item.test.source === extractCssOptions.test.source | ||
); | ||
if (moduleExtractCssOptions) { | ||
extractCssOptions = moduleExtractCssOptions; | ||
} else { | ||
this.options.modules.unshift(extractCssOptions); | ||
} | ||
this.options.extractCss = extractCssOptions; | ||
this.options.extractJs = options.js || {}; | ||
// let know the loader that the plugin is being used | ||
PluginService.init(this.options); | ||
PluginService.init(Options); | ||
@@ -215,59 +156,2 @@ // bind the instance context for using these methods as references in Webpack hooks | ||
/** | ||
* Entries defined in plugin options are merged with Webpack entry option. | ||
* | ||
* @param {{}} options The Webpack options where entry contains additional 'data' property. | ||
*/ | ||
initializeWebpackEntry(options) { | ||
let { entry } = options; | ||
const pluginEntry = this.options.entry; | ||
// check whether the entry in config is empty, defaults Webpack set structure: `{ main: {} }`, | ||
if (Object.keys(entry).length === 1 && Object.keys(Object.entries(entry)[0][1]).length === 0) { | ||
// set empty entry to avoid Webpack error | ||
entry = {}; | ||
} | ||
if (pluginEntry) { | ||
for (const key in pluginEntry) { | ||
const entry = pluginEntry[key]; | ||
if (entry.import == null) { | ||
pluginEntry[key] = { import: [entry] }; | ||
continue; | ||
} | ||
if (!Array.isArray(entry.import)) { | ||
entry.import = [entry.import]; | ||
} | ||
} | ||
options.entry = { ...entry, ...pluginEntry }; | ||
// the 'layer' entry property is only allowed when 'experiments.layers' is enabled | ||
options.experiments.layers = true; | ||
} | ||
} | ||
/** | ||
* Get plugin module depend on type of source file. | ||
* | ||
* @param {string} sourceFile The source file of asset. | ||
* @returns {ModuleOptions|undefined} | ||
*/ | ||
getModule(sourceFile) { | ||
return this.options.modules.find((module) => module.enabled !== false && module.test.test(sourceFile)); | ||
} | ||
/** | ||
* Whether the source file is an entry file. | ||
* | ||
* @param {string} sourceFile | ||
* @return {boolean} | ||
*/ | ||
isEntry(sourceFile) { | ||
const [file] = sourceFile.split('?', 1); | ||
return this.options.test.test(file); | ||
} | ||
/** | ||
* Apply plugin. | ||
@@ -277,44 +161,19 @@ * @param {{}} compiler | ||
apply(compiler) { | ||
if (!this.enabled) return; | ||
if (!Options.isEnabled()) return; | ||
this.initialize(compiler); | ||
const { webpack } = compiler; | ||
const { webpack, options } = compiler; | ||
const webpackOutput = options.output; | ||
const { extractJs } = this.options; | ||
RawSource = webpack.sources.RawSource; | ||
HotUpdateChunk = webpack.HotUpdateChunk; | ||
// save using webpack options | ||
this.webpackContext = options.context; | ||
this.webpackOutputPath = webpackOutput.path || path.join(__dirname, 'dist'); | ||
Options.initWebpack(compiler.options); | ||
Options.enableLibraryType(this.entryLibrary.type); | ||
this.initialize(compiler); | ||
// define js output filename | ||
if (!webpackOutput.filename) { | ||
webpackOutput.filename = '[name].js'; | ||
} | ||
if (extractJs.filename) { | ||
webpackOutput.filename = extractJs.filename; | ||
} else { | ||
extractJs.filename = webpackOutput.filename; | ||
} | ||
// resolve js filename by outputPath | ||
if (extractJs.outputPath) { | ||
const filename = extractJs.filename; | ||
extractJs.filename = isFunction(filename) | ||
? (pathData, assetInfo) => this.resolveOutputFilename(filename(pathData, assetInfo), extractJs.outputPath) | ||
: this.resolveOutputFilename(extractJs.filename, extractJs.outputPath); | ||
webpackOutput.filename = extractJs.filename; | ||
} | ||
Asset.init({ | ||
outputPath: this.webpackOutputPath, | ||
publicPath: webpackOutput.publicPath, | ||
outputPath: Options.webpackOptions.output.path, | ||
publicPath: Options.webpackOptions.output.publicPath, | ||
}); | ||
AssetResource.init(compiler); | ||
AssetEntry.setWebpackOutputPath(this.webpackOutputPath); | ||
AssetEntry.setWebpackOutputPath(Options.webpackOptions.output.path); | ||
@@ -326,14 +185,2 @@ // clear caches by tests for webpack serve/watch | ||
// enable library type | ||
const libraryType = this.entryLibrary.type; | ||
if (webpackOutput.enabledLibraryTypes.indexOf(libraryType) < 0) { | ||
webpackOutput.enabledLibraryTypes.push(libraryType); | ||
} | ||
if (!this.options.sourcePath) this.options.sourcePath = this.webpackContext; | ||
if (!this.options.outputPath) this.options.outputPath = this.webpackOutputPath; | ||
// options.entry | ||
this.initializeWebpackEntry(options); | ||
// entry options | ||
@@ -344,10 +191,4 @@ compiler.hooks.entryOption.tap(pluginName, this.afterProcessEntry); | ||
compiler.hooks.watchRun.tap(pluginName, (compiler) => { | ||
Options.initWatchMode(); | ||
PluginService.setWatchMode(true); | ||
const { publicPath } = compiler.options.output; | ||
if (publicPath == null || publicPath === 'auto') { | ||
// By using watch/serve browsers not support an automatic publicPath in HMR script injected into inlined JS, | ||
// the output.publicPath must be an empty string. | ||
compiler.options.output.publicPath = ''; | ||
} | ||
}); | ||
@@ -361,3 +202,3 @@ | ||
fs: normalModuleFactory.fs.fileSystem, | ||
rootContext: this.webpackContext, | ||
rootContext: Options.rootPath, | ||
}); | ||
@@ -385,4 +226,7 @@ | ||
const [, entryDataId] = module.layer.split('=', 2); | ||
loaderContext.entryData = this.entryDataCache.get(entryDataId); | ||
loaderContext.data = this.entryDataCache.get(entryDataId); | ||
// loaderContext.entryData = this.entryDataCache.get(entryDataId); | ||
// loaderContext.data = this.entryDataCache.get(entryDataId); | ||
loaderContext.entryData = AssetEntry.getData(entryDataId); | ||
loaderContext.data = AssetEntry.getData(entryDataId); | ||
} | ||
@@ -421,64 +265,3 @@ }); | ||
afterProcessEntry(context, entries) { | ||
const extensionRegexp = this.options.test; | ||
for (let name in entries) { | ||
const entry = entries[name]; | ||
let { filename: filenameTemplate, sourcePath, outputPath, postprocess, extract, verbose } = this.options; | ||
const importFile = entry.import[0]; | ||
let request = importFile; | ||
let [sourceFile] = importFile.split('?', 1); | ||
const module = this.getModule(sourceFile); | ||
if (!extensionRegexp.test(sourceFile) && !module) continue; | ||
if (!entry.library) entry.library = this.entryLibrary; | ||
if (module) { | ||
if (module.hasOwnProperty('verbose')) verbose = module.verbose; | ||
if (module.filename) filenameTemplate = module.filename; | ||
if (module.sourcePath) sourcePath = module.sourcePath; | ||
if (module.outputPath) outputPath = module.outputPath; | ||
if (module.postprocess) postprocess = module.postprocess; | ||
if (module.extract) extract = module.extract; | ||
} | ||
if (entry.filename) filenameTemplate = entry.filename; | ||
if (!path.isAbsolute(sourceFile)) { | ||
request = path.join(sourcePath, request); | ||
sourceFile = path.join(sourcePath, sourceFile); | ||
entry.import[0] = path.join(sourcePath, importFile); | ||
} | ||
/** @type {AssetEntryOptions} */ | ||
const assetEntryOptions = { | ||
name, | ||
filenameTemplate, | ||
filename: undefined, | ||
file: undefined, | ||
request, | ||
importFile: sourceFile, | ||
sourcePath, | ||
outputPath, | ||
library: entry.library, | ||
postprocess: isFunction(postprocess) ? postprocess : null, | ||
extract: isFunction(extract) ? extract : null, | ||
verbose, | ||
}; | ||
if (entry.data) { | ||
// IMPORTANT: when the entry contains same source file for many chunks, for example: | ||
// { | ||
// page1: { import: 'src/template.html', data: { title: 'A'} }, | ||
// page2: { import: 'src/template.html', data: { title: 'B'} }, | ||
// } | ||
// add an unique identifier of the entry to execute a loader for all templates, | ||
// otherwise Webpack call a loader only for the first template. | ||
// See 'webpack/lib/NormalModule.js:identifier()'. | ||
entry.layer = `__entryDataId=${this.entryDataIndex}`; | ||
this.entryDataCache.set(`${this.entryDataIndex}`, entry.data); | ||
this.entryDataIndex++; | ||
} | ||
AssetEntry.add(entry, assetEntryOptions); | ||
} | ||
AssetEntry.addEntries(entries, { entryLibrary: this.entryLibrary }); | ||
} | ||
@@ -500,10 +283,8 @@ | ||
if (issuer && issuer.length > 0) { | ||
const extractCss = this.options.extractCss; | ||
if (extractCss.enabled && extractCss.test.test(issuer) && resourceFile.endsWith('.js')) { | ||
// ignore runtime scripts of a loader, because a style can't have a javascript dependency | ||
return false; | ||
} | ||
if (issuer) { | ||
// ignore and exclude from compilation the runtime script of a css loader | ||
if (Options.isStyle(issuer) && resourceFile.endsWith('.js')) return false; | ||
const scriptFile = AssetScript.resolveFile(request); | ||
if (scriptFile) { | ||
@@ -514,3 +295,3 @@ const name = AssetScript.getUniqueName(scriptFile); | ||
importFile: scriptFile, | ||
filenameTemplate: this.options.extractJs.filename, | ||
filenameTemplate: Options.getJs().filename, | ||
context, | ||
@@ -544,3 +325,3 @@ issuer, | ||
filterAlternativeRequests(requests, options) { | ||
return requests.filter((item) => !this.isEntry(item.request)); | ||
return requests.filter((item) => !Options.isEntry(item.request)); | ||
} | ||
@@ -563,3 +344,3 @@ | ||
if (type === 'asset/inline' || type === 'asset') { | ||
AssetInline.add(resource, issuer, this.isEntry(issuer)); | ||
AssetInline.add(resource, issuer, Options.isEntry(issuer)); | ||
} | ||
@@ -620,3 +401,4 @@ | ||
renderManifest(result, { chunk, chunkGraph, outputOptions, codeGenerationResults }) { | ||
const { compilation, verbose } = this; | ||
const { compilation } = this; | ||
const verbose = Options.isVerbose(); | ||
@@ -647,3 +429,3 @@ if (chunk instanceof HotUpdateChunk) return; | ||
const [sourceFile] = sourceRequest.split('?', 1); | ||
let issuerFile = !issuer || this.isEntry(issuer) ? entry.importFile : issuer; | ||
let issuerFile = !issuer || Options.isEntry(issuer) ? entry.importFile : issuer; | ||
@@ -654,9 +436,9 @@ if (module.type === 'javascript/auto') { | ||
if (entry.importFile === issuer) { | ||
const verbose = this.options.extractJs.verbose || entry.verbose; | ||
const { verbose, outputPath } = Options.getJs(); | ||
if (verbose && issuerFile !== sourceFile) { | ||
if ((verbose || entry.verbose) && issuerFile !== sourceFile) { | ||
verboseList.add({ | ||
type: 'script', | ||
sourceFile, | ||
outputPath: this.options.extractJs.outputPath || this.webpackOutputPath, | ||
outputPath, | ||
}); | ||
@@ -718,3 +500,3 @@ } | ||
// asset supported via the plugin module | ||
const pluginModule = this.getModule(sourceFile); | ||
const pluginModule = Options.getModule(sourceFile); | ||
if (pluginModule == null) continue; | ||
@@ -752,3 +534,3 @@ | ||
if (pluginModule.test.test(sourceFile)) { | ||
assetFile = this.resolveOutputFilename(assetFile, pluginModule.outputPath); | ||
assetFile = Options.resolveOutputFilename(assetFile, pluginModule.outputPath); | ||
} | ||
@@ -837,9 +619,7 @@ | ||
afterProcessAssets() { | ||
const options = this.options; | ||
const compilation = this.compilation; | ||
const { compiler, assets } = compilation; | ||
const { RawSource } = compiler.webpack.sources; | ||
const afterProcess = typeof options.afterProcess === 'function' ? options.afterProcess : null; | ||
if (options.extractComments !== true) { | ||
if (!Options.isExtractComments()) { | ||
AssetTrash.removeComments(compilation); | ||
@@ -856,12 +636,5 @@ } | ||
let newContent = this.afterProcess(compilation, { sourceFile, assetFile, source }); | ||
let res = Options.afterProcess(newContent || source, { sourceFile, assetFile }); | ||
if (afterProcess && assetFile.endsWith('.html')) { | ||
try { | ||
// TODO: test not yet documented experimental feature and rename it to other name | ||
let result = afterProcess(newContent || source, { sourceFile, assetFile }); | ||
if (result) newContent = result; | ||
} catch (err) { | ||
throw new Error(err); | ||
} | ||
} | ||
if (res != null) newContent = res; | ||
@@ -924,3 +697,3 @@ if (newContent != null) { | ||
require: Resolver.require, | ||
// the `module.id` is required for `css-loader`, in module extractCss expected as source path | ||
// the `module.id` is required for `css-loader`, in module extract CSS expected as the source path | ||
module: { id: sourceFile }, | ||
@@ -1011,3 +784,3 @@ }; | ||
sourceFile, | ||
outputPath: this.webpackOutputPath, | ||
outputPath: Options.webpackOptions.output.path, | ||
}); | ||
@@ -1035,24 +808,4 @@ break; | ||
} | ||
/** | ||
* @param {string} filename The output filename. | ||
* @param {string | null} outputPath The output path. | ||
* @return {string} | ||
*/ | ||
resolveOutputFilename(filename, outputPath) { | ||
if (!outputPath) return filename; | ||
let relativeOutputPath = path.isAbsolute(outputPath) | ||
? path.relative(this.webpackOutputPath, outputPath) | ||
: outputPath; | ||
if (relativeOutputPath) { | ||
if (isWin) relativeOutputPath = pathToPosix(relativeOutputPath); | ||
filename = path.posix.join(relativeOutputPath, filename); | ||
} | ||
return filename; | ||
} | ||
} | ||
module.exports = AssetCompiler; |
const path = require('path'); | ||
const { isFunction } = require('./Utils'); | ||
const Options = require('./Options'); | ||
/** | ||
* @typedef {Object} AssetEntryOptions | ||
* @property {string} name The key of webpack entry. | ||
* @property {string} file The output asset file with absolute path. | ||
* @property {string} assetFile The output asset file with relative path by webpack output path. | ||
* Note: the method compilation.emitAsset() use this file as key of assets object | ||
* and save the file relative by output path, defined in webpack.options.output.path. | ||
* @property {string|function(PathData, AssetInfo): string} filenameTemplate The filename template or function. | ||
* @property {string} filename The asset filename. | ||
* The template strings support only these substitutions: [name], [base], [path], [ext], [id], [contenthash], [contenthash:nn] | ||
* See https://webpack.js.org/configuration/output/#outputfilename | ||
* @property {string} request The full path of import file with query. | ||
* @property {string} importFile The import file only w/o query. | ||
* @property {string} outputPath | ||
* @property {string} sourcePath | ||
* @property {{name: string, type: string}} library Define the output a js file. | ||
* See https://webpack.js.org/configuration/output/#outputlibrary | ||
* @property {function(string, AssetInfo, Compilation): string} [postprocess = null] The post process for extracted content from entry. | ||
* @property {function(): string|null =} extract | ||
* @property {Array} resources | ||
* @property {boolean|string} [verbose = false] Show an information by handles of the entry in a postprocess. | ||
*/ | ||
class AssetEntry { | ||
@@ -12,2 +36,6 @@ /** @type {Map<string, AssetEntryOptions>} */ | ||
// the id to bind loader with data passed into template via entry.data | ||
static dataIndex = 1; | ||
static data = new Map(); | ||
/** | ||
@@ -36,7 +64,81 @@ * @param {string} outputPath The Webpack output path. | ||
static getData(id) { | ||
return this.data.get(id); | ||
} | ||
/** | ||
* @param {Array<Object>} entries | ||
* @param {Object} entryLibrary | ||
*/ | ||
static addEntries(entries, { entryLibrary }) { | ||
for (let name in entries) { | ||
const entry = entries[name]; | ||
const importFile = entry.import[0]; | ||
let request = importFile; | ||
let [sourceFile] = importFile.split('?', 1); | ||
const module = Options.getModule(sourceFile); | ||
let { filename: filenameTemplate, sourcePath, outputPath, postprocess, extract } = Options.get(); | ||
let verbose = Options.isVerbose(); | ||
if (!Options.isEntry(sourceFile) && !module) continue; | ||
if (!entry.library) entry.library = entryLibrary; | ||
if (module) { | ||
if (module.hasOwnProperty('verbose')) verbose = Options.isTrue(module.verbose, false); | ||
if (module.filename) filenameTemplate = module.filename; | ||
if (module.sourcePath) sourcePath = module.sourcePath; | ||
if (module.outputPath) outputPath = module.outputPath; | ||
if (module.postprocess) postprocess = module.postprocess; | ||
if (module.extract) extract = module.extract; | ||
} | ||
if (entry.filename) filenameTemplate = entry.filename; | ||
if (!path.isAbsolute(sourceFile)) { | ||
request = path.join(sourcePath, request); | ||
sourceFile = path.join(sourcePath, sourceFile); | ||
entry.import[0] = path.join(sourcePath, importFile); | ||
} | ||
/** @type {AssetEntryOptions} */ | ||
const assetEntryOptions = { | ||
name, | ||
filenameTemplate, | ||
filename: undefined, | ||
file: undefined, | ||
request, | ||
importFile: sourceFile, | ||
sourcePath, | ||
outputPath, | ||
library: entry.library, | ||
postprocess: isFunction(postprocess) ? postprocess : null, | ||
extract: isFunction(extract) ? extract : null, | ||
verbose, | ||
}; | ||
if (entry.data) { | ||
// IMPORTANT: when the entry contains same source file for many chunks, for example: | ||
// { | ||
// page1: { import: 'src/template.html', data: { title: 'A'} }, | ||
// page2: { import: 'src/template.html', data: { title: 'B'} }, | ||
// } | ||
// add an unique identifier of the entry to execute a loader for all templates, | ||
// otherwise Webpack call a loader only for the first template. | ||
// See 'webpack/lib/NormalModule.js:identifier()'. | ||
entry.layer = `__entryDataId=${this.dataIndex}`; | ||
this.data.set(`${this.dataIndex}`, entry.data); | ||
this.dataIndex++; | ||
} | ||
this.#add(entry, assetEntryOptions); | ||
} | ||
} | ||
/** | ||
* @param {{}} entry The webpack entry object. | ||
* @param {AssetEntryOptions} assetEntryOptions | ||
* @private | ||
*/ | ||
static add(entry, assetEntryOptions) { | ||
static #add(entry, assetEntryOptions) { | ||
const { name, outputPath, filenameTemplate } = assetEntryOptions; | ||
@@ -79,3 +181,3 @@ const relativeOutputPath = path.isAbsolute(outputPath) | ||
// skip duplicate entries | ||
if (this.inEntry(name, importFile)) return false; | ||
if (this.#exists(name, importFile)) return false; | ||
@@ -109,3 +211,3 @@ const entry = { | ||
this.add(entry, assetEntryOptions); | ||
this.#add(entry, assetEntryOptions); | ||
this.compilationEntryNames.add(name); | ||
@@ -141,4 +243,5 @@ | ||
* @return {boolean} | ||
* @private | ||
*/ | ||
static inEntry(name, file) { | ||
static #exists(name, file) { | ||
const entry = this.entryMap.get(name); | ||
@@ -145,0 +248,0 @@ return entry && entry.importFile === file; |
@@ -5,3 +5,3 @@ const { red, green, black } = require('ansis/colors'); | ||
const header = `\n${black.bgYellow`[${pluginName}] DEPRECATE`} `; | ||
const header = `\n${black.bgYellow` ${pluginName} `}${black.bgAnsi(227)` DEPRECATE `} `; | ||
@@ -8,0 +8,0 @@ const deprecateOptionExtractCss = () => { |
const fs = require('fs'); | ||
const path = require('path'); | ||
const { red, green, cyan, cyanBright, yellow, white, blueBright } = require('ansis/colors'); | ||
const { green, cyan, cyanBright, yellow, white, blueBright, black } = require('ansis/colors'); | ||
const { pluginName } = require('../../config'); | ||
const header = `\n${red`[${pluginName}]`}`; | ||
const header = `\n${black.bgRedBright` ${pluginName} `}`; | ||
let lastError = null; | ||
@@ -8,0 +8,0 @@ |
const { pluginName } = require('../../config'); | ||
const { pathRelativeByPwd, outToConsole, isFunction } = require('../Utils'); | ||
const { | ||
green, | ||
greenBright, | ||
cyan, | ||
cyanBright, | ||
magenta, | ||
yellowBright, | ||
black, | ||
gray, | ||
ansi, | ||
yellow, | ||
} = require('ansis/colors'); | ||
const { green, greenBright, cyan, cyanBright, magenta, yellowBright, black, ansi, yellow } = require('ansis/colors'); | ||
const Asset = require('../Asset'); | ||
const grayBright = ansi(245); | ||
const header = black.bgGreen`[${pluginName}]`; | ||
const header = black.bgGreen` ${pluginName} `; | ||
@@ -20,0 +9,0 @@ // max width of labels in first column |
@@ -5,3 +5,3 @@ const { black, yellow, cyan, magenta } = require('ansis/colors'); | ||
const header = `\n${black.bgYellow`[${pluginName}] WARNING`} `; | ||
const header = `\n${black.bgYellow` ${pluginName} `}${black.bgAnsi(227)` WARNING `} `; | ||
@@ -8,0 +8,0 @@ const duplicateScriptWarning = (request, issuer) => { |
@@ -5,8 +5,13 @@ /** | ||
*/ | ||
/** @typedef {import('Options')} PluginOptionInstance */ | ||
class PluginService { | ||
/** @type PluginOptionInstance Provide to use the plugin option instance in the loader. */ | ||
static #options = null; | ||
static used = false; | ||
static options = null; | ||
static contextCache = new Set(); | ||
static compiler = null; | ||
static watchMode = false; | ||
static watchMode; | ||
@@ -20,7 +25,8 @@ /** | ||
* | ||
* @param {{}} options The options of the plugin. | ||
* @param {PluginOptionInstance} options The plugin options instance. | ||
*/ | ||
static init(options) { | ||
this.used = true; | ||
this.options = options; | ||
this.#options = options; | ||
this.watchMode = false; | ||
} | ||
@@ -36,21 +42,12 @@ | ||
/** | ||
* Returns plugin options. | ||
* Returns plugin options instance. | ||
* | ||
* @return {null} | ||
* @return {PluginOptionInstance} | ||
*/ | ||
static getOptions() { | ||
return this.options; | ||
return this.#options; | ||
} | ||
/** | ||
* Return a list of resolve restrictions to restrict the paths that a request can be resolved on. | ||
* @see https://webpack.js.org/configuration/resolve/#resolverestrictions | ||
* @return {Array<RegExp|string>} | ||
*/ | ||
static getStyleRestrictions() { | ||
return this.options ? [this.options.extractCss?.test] : []; | ||
} | ||
/** | ||
* Whether is the plugin used. | ||
* Whether the plugin is defined in Webpack configuration. | ||
* @return {boolean} | ||
@@ -57,0 +54,0 @@ */ |
@@ -1,22 +0,4 @@ | ||
const path = require('path'); | ||
const { isWin, isFunction, pathRelativeByPwd, pathToPosix, parseQuery, outToConsole } = require('../Common/Helpers'); | ||
const { isWin, isFunction, workingDir, pathToPosix, parseQuery, outToConsole } = require('../Common/Helpers'); | ||
/** | ||
* Return path of file relative by working directory. | ||
* | ||
* @param {string} file | ||
* @return {string} | ||
*/ | ||
const pathRelativeByPwd = (file) => { | ||
if (file.startsWith(workingDir)) { | ||
let relPath = path.relative(workingDir, file); | ||
return path.extname(file) ? relPath : path.join(relPath, path.sep); | ||
} | ||
return file; | ||
}; | ||
/** | ||
* Parse resource path and raw query from request. | ||
@@ -23,0 +5,0 @@ * |
Sorry, the diff of this file is too big to display
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
237128
39
4406
2260
6