html-bundler-webpack-plugin
Advanced tools
Comparing version 1.5.2 to 1.6.0
# Change log | ||
## 1.6.0 (2023-03-06) | ||
- feat: add `css.inline` option, replaces the functionality of `style-loader`.\ | ||
The values of `inline` option: | ||
- false - extract processed CSS in an output file, defaults | ||
- true - inline processed CSS into HTML via `style` tag | ||
- 'auto' - in development mode - inline CSS, in production mode - extract in a file | ||
- feat: add `js.inline` option to inline extracted JS into HTML | ||
- feat: add to the `?inline` query parameter for JS and CSS files the values: `false`, `true`, `'auto'`.\ | ||
Note: the query parameter takes precedence over global `js.inline` or `css.inline` option. | ||
- fix: emit a loader exception as an instance of Error instead a string | ||
- fix: throw exception when the loader is used but the `HtmlBundlerPlugin` is not initialized in Webpack plugins option | ||
- refactor: optimize and improve the code | ||
- test: add tests for inline CSS and JS | ||
- docs: update readme with new features | ||
## 1.5.2 (2023-03-03) | ||
@@ -4,0 +19,0 @@ - fix: correct loader export when template contain CRLF line separators |
{ | ||
"name": "html-bundler-webpack-plugin", | ||
"version": "1.5.2", | ||
"version": "1.6.0", | ||
"description": "HTML bundler plugin for webpack handels HTML template as entry point, extracts CSS and JS from their sources specified in HTML.", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -7,3 +7,3 @@ const path = require('path'); | ||
const Options = require('./Options'); | ||
const { preprocessorErrorToString, compileErrorToString, exportErrorToString } = require('./Messages/Exeptions'); | ||
const { notInitializedPluginError, preprocessorError, compileError, exportError } = require('./Messages/Exeptions'); | ||
@@ -23,6 +23,6 @@ const RenderMethod = require('./methods/RenderMethod'); | ||
const callback = (error, result) => { | ||
const callback = (error, result = null) => { | ||
if (error) { | ||
// in build mode, abort the compilation process and display an error in the output | ||
if (!PluginService.isWatchMode()) return loaderCallback(error); | ||
if (result == null || !PluginService.isWatchMode()) return loaderCallback(error); | ||
@@ -35,2 +35,7 @@ // in watch mode, display an error in the output without abort the compilation process | ||
if (!PluginService.isUsed()) { | ||
callback(notInitializedPluginError()); | ||
return; | ||
} | ||
new Promise((resolve) => { | ||
@@ -73,13 +78,12 @@ // the options must be initialized before others | ||
const issuer = path.relative(rootContext, resourcePath); | ||
let message; | ||
switch (errorStage) { | ||
case 'preprocessor': | ||
message = preprocessorErrorToString(error, issuer); | ||
error = preprocessorError(error, issuer); | ||
break; | ||
case 'compile': | ||
message = compileErrorToString(error, issuer); | ||
error = compileError(error, issuer); | ||
break; | ||
case 'export': | ||
message = exportErrorToString(error, issuer); | ||
error = exportError(error, issuer); | ||
break; | ||
@@ -89,12 +93,12 @@ default: | ||
const method = new RenderMethod({}); | ||
const browserError = method.exportError(error, resource); | ||
callback(error, browserError); | ||
const browserErrorMessage = method.exportError(error, resource); | ||
callback(error, browserErrorMessage); | ||
return; | ||
} | ||
const browserError = Loader.exportError(message, resource); | ||
const browserErrorMessage = Loader.exportError(error, resource); | ||
Dependency.init(loaderContext); | ||
Dependency.watch(); | ||
callback(message, browserError); | ||
callback(error, browserErrorMessage); | ||
}); | ||
@@ -101,0 +105,0 @@ }; |
@@ -95,8 +95,8 @@ const { merge } = require('webpack-merge'); | ||
* | ||
* @param {string} message | ||
* @param {Error} error | ||
* @param {string} issuer The issuer where the error occurred. | ||
* @return {string} | ||
*/ | ||
static exportError(message, issuer) { | ||
return this.compiler?.exportError(message, issuer) || message.toString(); | ||
static exportError(error, issuer) { | ||
return this.compiler?.exportError(error, issuer) || error.toString(); | ||
} | ||
@@ -103,0 +103,0 @@ } |
const ansis = require('ansis'); | ||
const { bgRed, redBright, yellow, cyan, green } = require('ansis/colors'); | ||
const { bgRed, yellow, cyan, green, ansi256, cyanBright } = require('ansis/colors'); | ||
const { pluginName } = require('../../config'); | ||
const redBright = ansi256(203); | ||
const pluginHeaderHtml = `<span style="color:#e36049">[${pluginName}]</span>`; | ||
@@ -83,8 +85,23 @@ const pluginHeader = `\n${bgRed.whiteBright` ${pluginName} `}`; | ||
/** | ||
* @returns {Error} | ||
*/ | ||
const notInitializedPluginError = () => { | ||
return new LoaderException( | ||
redBright( | ||
`${pluginHeader} Illegal usage of the loader.\n` + | ||
`This loader should be used only with the ${green`HtmlBundlerPlugin`} initialized in the ${yellow`Webpack 'plugins' option`}!\n` + | ||
`See the documentation: ${cyanBright`https://github.com/webdiscus/html-bundler-webpack-plugin`}` | ||
) | ||
); | ||
}; | ||
/** | ||
* @param {Error} error | ||
* @param {string} file | ||
* @returns {string} | ||
* @returns {Error} | ||
*/ | ||
const preprocessorErrorToString = (error, file) => { | ||
return `${pluginHeader} Preprocessor failed\nFile: ${cyan(file)}\n` + error.toString(); | ||
const preprocessorError = (error, file) => { | ||
return new LoaderException( | ||
`${pluginHeader} Preprocessor failed\nFile: ${cyan(file)}\n` + (error.stack || error.message) | ||
); | ||
}; | ||
@@ -95,6 +112,8 @@ | ||
* @param {string} file | ||
* @returns {string} | ||
* @returns {Error} | ||
*/ | ||
const compileErrorToString = (error, file) => { | ||
return `${pluginHeader} Template compilation failed\nFile: ${cyan(file)}\n` + error.toString(); | ||
const compileError = (error, file) => { | ||
return new LoaderException( | ||
`${pluginHeader} Template compilation failed\nFile: ${cyan(file)}\n` + (error.stack || error.message) | ||
); | ||
}; | ||
@@ -105,6 +124,8 @@ | ||
* @param {string} file | ||
* @returns {string} | ||
* @returns {Error} | ||
*/ | ||
const exportErrorToString = (error, file) => { | ||
return `${pluginHeader} Export of compiled template failed\nFile: ${cyan(file)}\n` + error.toString(); | ||
const exportError = (error, file) => { | ||
return new LoaderException( | ||
`${pluginHeader} Export of compiled template failed\nFile: ${cyan(file)}\n` + (error.stack || error.message) | ||
); | ||
}; | ||
@@ -117,5 +138,6 @@ | ||
watchPathsException, | ||
preprocessorErrorToString, | ||
compileErrorToString, | ||
exportErrorToString, | ||
notInitializedPluginError, | ||
preprocessorError, | ||
compileError, | ||
exportError, | ||
}; |
@@ -0,4 +1,5 @@ | ||
const PluginService = require('../Plugin/PluginService'); | ||
const Loader = require('./Loader'); | ||
const Options = require('./Options'); | ||
const { isWin, isInline, pathToPosix } = require('./Utils'); | ||
const { isWin, pathToPosix } = require('./Utils'); | ||
@@ -73,2 +74,3 @@ const spaceChars = [' ', '\t', '\n', '\r', '\f']; | ||
static sources = []; | ||
static issuer; | ||
@@ -172,2 +174,3 @@ /** | ||
static normalizeTagsList(tags) { | ||
const pluginOptions = PluginService.getOptions(); | ||
const result = []; | ||
@@ -195,3 +198,6 @@ | ||
if (['style', 'script'].indexOf(type) >= 0 && isInline(value)) { | ||
if ( | ||
(type === 'script' && pluginOptions.isInlineJs(value)) || | ||
(type === 'style' && pluginOptions.isInlineCss(value)) | ||
) { | ||
// cut the source tag completely to inject an inline `<script>` or `<style>` tag | ||
@@ -198,0 +204,0 @@ startPos = tagStartPos; |
@@ -8,13 +8,2 @@ const path = require('path'); | ||
/** | ||
* Whether request contains `inline` param. | ||
* | ||
* @param {string} request | ||
* @return {boolean} | ||
*/ | ||
const isInline = (request) => { | ||
const [, query] = request.split('?', 2); | ||
return query != null && /(?:^|&)inline(?:$|&)/.test(query); | ||
}; | ||
module.exports = { | ||
@@ -25,4 +14,3 @@ isWin, | ||
hmrFile, | ||
isInline, | ||
parseQuery, | ||
}; |
@@ -1,4 +0,1 @@ | ||
const path = require('path'); | ||
const { isWin, pathToPosix } = require('./Utils'); | ||
class Asset { | ||
@@ -21,85 +18,2 @@ /** | ||
/** | ||
* | ||
* @param {string} outputPath | ||
* @param {string | undefined} publicPath | ||
*/ | ||
static init({ outputPath, publicPath }) { | ||
if (typeof publicPath === 'function') { | ||
publicPath = publicPath.call(null, {}); | ||
} | ||
this.outputPath = outputPath; | ||
this.publicPath = publicPath === undefined ? 'auto' : publicPath; | ||
// reset initial states | ||
this.isAutoPublicPath = false; | ||
this.isUrlPublicPath = false; | ||
this.isRelativePublicPath = false; | ||
if (this.publicPath === 'auto') { | ||
this.isAutoPublicPath = true; | ||
} else if (/^(\/\/|https?:\/\/)/i.test(this.publicPath)) { | ||
this.isUrlPublicPath = true; | ||
} else if (!this.publicPath.startsWith('/')) { | ||
this.isRelativePublicPath = true; | ||
} | ||
} | ||
/** | ||
* Reset settings. | ||
* Called before each compilation after changes by `webpack serv/watch`. | ||
*/ | ||
static reset() { | ||
this.fileIndex = {}; | ||
this.files.clear(); | ||
} | ||
/** | ||
* Get the publicPath. | ||
* | ||
* @param {string | null} assetFile The output asset filename relative by output path. | ||
* @return {string} | ||
*/ | ||
static getPublicPath(assetFile = null) { | ||
let isAutoRelative = assetFile && this.isRelativePublicPath && !assetFile.endsWith('.html'); | ||
if (this.isAutoPublicPath || isAutoRelative) { | ||
if (!assetFile) return ''; | ||
const fullFilename = path.resolve(this.outputPath, assetFile); | ||
const context = path.dirname(fullFilename); | ||
const publicPath = path.relative(context, this.outputPath) + '/'; | ||
return isWin ? pathToPosix(publicPath) : publicPath; | ||
} | ||
return this.publicPath; | ||
} | ||
/** | ||
* Get the output asset file regards the publicPath. | ||
* | ||
* @param {string} assetFile The output asset filename relative by output path. | ||
* @param {string} issuer The output issuer filename relative by output path. | ||
* @return {string} | ||
*/ | ||
static getOutputFile(assetFile, issuer) { | ||
let isAutoRelative = issuer && this.isRelativePublicPath && !issuer.endsWith('.html'); | ||
// if public path is relative, then a resource using not in the template file must be auto resolved | ||
if (this.isAutoPublicPath || isAutoRelative) { | ||
if (!issuer) return assetFile; | ||
const issuerFullFilename = path.resolve(this.outputPath, issuer); | ||
const context = path.dirname(issuerFullFilename); | ||
const file = path.posix.join(this.outputPath, assetFile); | ||
const outputFilename = path.relative(context, file); | ||
return isWin ? pathToPosix(outputFilename) : outputFilename; | ||
} | ||
return this.publicPath + '' + assetFile; | ||
} | ||
/** | ||
* Add resolved module asset. | ||
@@ -192,15 +106,8 @@ * This asset can be as issuer for other resource assets. | ||
/** | ||
* Whether request contains the `inline` URL query param. | ||
* | ||
* TODO: compare perf with url parse. | ||
* | ||
* @param {string} request | ||
* @return {boolean} | ||
* Reset settings. | ||
* Called before each compilation after changes by `webpack serv/watch`. | ||
*/ | ||
static isInline(request) { | ||
if (!request) return false; | ||
const [, query] = request.split('?', 2); | ||
return query != null && /(?:^|&)inline(?:$|&)/.test(query); | ||
static reset() { | ||
this.fileIndex = {}; | ||
this.files.clear(); | ||
} | ||
@@ -207,0 +114,0 @@ } |
@@ -56,3 +56,3 @@ const vm = require('vm'); | ||
* @property {function(string, ResourceInfo, Compilation): string|null =} postprocess The post process for extracted content from entry. | ||
* @property {function(sourceMaps: string, assetFile: string, compilation: Compilation): string|null =} extract | ||
* @property {function(compilation: Compilation, {source: string, assetFile: string, inline: boolean} ): string|null =} extract | ||
*/ | ||
@@ -164,8 +164,3 @@ | ||
Asset.init({ | ||
outputPath: Options.webpackOptions.output.path, | ||
publicPath: Options.webpackOptions.output.publicPath, | ||
}); | ||
AssetResource.init(compiler); | ||
AssetEntry.setWebpackOutputPath(Options.webpackOptions.output.path); | ||
@@ -177,5 +172,2 @@ // clear caches by tests for webpack serve/watch | ||
// entry options | ||
compiler.hooks.entryOption.tap(pluginName, this.afterProcessEntry); | ||
// executes by watch/serve only, before the compilation | ||
@@ -187,2 +179,5 @@ compiler.hooks.watchRun.tap(pluginName, (compiler) => { | ||
// entry options | ||
compiler.hooks.entryOption.tap(pluginName, this.afterProcessEntry); | ||
// this compilation | ||
@@ -246,2 +241,4 @@ compiler.hooks.thisCompilation.tap(pluginName, (compilation, { normalModuleFactory, contextModuleFactory }) => { | ||
compiler.hooks.done.tap(pluginName, this.done); | ||
compiler.hooks.shutdown.tap(pluginName, this.shutdown); | ||
compiler.hooks.watchClose.tap(pluginName, this.shutdown); | ||
} | ||
@@ -411,3 +408,2 @@ | ||
const inline = Asset.isInline(resourceResolveData.query) || module.type === 'asset/source'; | ||
const { issuer } = resourceResolveData.context; | ||
@@ -418,3 +414,3 @@ const [sourceFile] = sourceRequest.split('?', 1); | ||
if (module.type === 'javascript/auto') { | ||
// do nothing for scripts because webpack itself compiles and extracts JS files from scripts | ||
// extract JS: do nothing for scripts because webpack itself compiles and extracts JS files from scripts | ||
if (AssetScript.isScript(module)) { | ||
@@ -457,3 +453,3 @@ if (entry.importFile === issuer) { | ||
assetModules.add({ | ||
inline, | ||
inline: false, | ||
// resourceInfo | ||
@@ -524,2 +520,5 @@ isEntry: true, | ||
// extract CSS | ||
const inline = Asset.isStyle(module) ? Options.isInlineCss(resourceResolveData.query) : false; | ||
if (inline) { | ||
@@ -529,3 +528,3 @@ AssetSource.add(sourceRequest); | ||
// skip already processed file assets, but all inline assets must be processed | ||
// skip already processed asset except inlined asset | ||
if (isCached && !inline) { | ||
@@ -697,4 +696,3 @@ continue; | ||
if (pluginModule.extract) { | ||
pluginModule.inline = inline; | ||
result = pluginModule.extract(result, assetFile, this.compilation); | ||
result = pluginModule.extract(this.compilation, { source: result, assetFile, inline }); | ||
} | ||
@@ -704,2 +702,3 @@ if (pluginModule.postprocess) { | ||
isEntry, | ||
inline, | ||
verbose, | ||
@@ -792,4 +791,11 @@ outputPath, | ||
} | ||
/** | ||
* Called when the compiler is closing or a watching compilation has stopped. | ||
*/ | ||
shutdown() { | ||
PluginService.shutdown(); | ||
} | ||
} | ||
module.exports = AssetCompiler; |
@@ -42,9 +42,2 @@ const path = require('path'); | ||
/** | ||
* @param {string} outputPath The Webpack output path. | ||
*/ | ||
static setWebpackOutputPath(outputPath) { | ||
this.webpackOutputPath = outputPath; | ||
} | ||
/** | ||
* @param {Compilation} compilation The instance of the webpack compilation. | ||
@@ -154,3 +147,3 @@ */ | ||
const relativeOutputPath = path.isAbsolute(outputPath) | ||
? path.relative(this.webpackOutputPath, outputPath) | ||
? path.relative(Options.getWebpackOutputPath(), outputPath) | ||
: outputPath; | ||
@@ -213,3 +206,3 @@ | ||
sourcePath: context, | ||
outputPath: this.webpackOutputPath, | ||
outputPath: Options.getWebpackOutputPath(), | ||
postprocess: undefined, | ||
@@ -216,0 +209,0 @@ extract: undefined, |
@@ -159,3 +159,3 @@ const path = require('path'); | ||
const chunkFile = chunkFiles.values().next().value; | ||
const assetFile = Asset.getOutputFile(chunkFile, issuerAssetFilename); | ||
const assetFile = Options.getAssetOutputFile(chunkFile, issuerAssetFilename); | ||
@@ -168,3 +168,4 @@ if (asset.inline === true) { | ||
newContent = content.slice(0, pos) + source + content.slice(pos + sourceFile.length); | ||
AssetTrash.add(assetFile); | ||
// note: the keys in compilation.assets are exact the chunkFile | ||
AssetTrash.add(chunkFile); | ||
} | ||
@@ -198,3 +199,3 @@ } else { | ||
if (!chunkScripts.has(chunkFile)) { | ||
const assetFile = Asset.getOutputFile(chunkFile, issuerAssetFilename); | ||
const assetFile = Options.getAssetOutputFile(chunkFile, issuerAssetFilename); | ||
@@ -201,0 +202,0 @@ if (scriptTags) scriptTags += LF; |
@@ -8,9 +8,9 @@ const path = require('path'); | ||
/** | ||
* @param {string} file | ||
* @param {string} resource The filename including a query. | ||
* @return {boolean} | ||
*/ | ||
static isInline(file) { | ||
static isInline(resource) { | ||
// fix: a file resolved in the loader is always in posix format, | ||
// but in the plugin the file can be in win format, therefore normalize the file | ||
return this.data.has(path.normalize(file)); | ||
return this.data.has(path.normalize(resource)); | ||
} | ||
@@ -17,0 +17,0 @@ |
@@ -0,1 +1,3 @@ | ||
const Options = require('./Options'); | ||
/** | ||
@@ -36,2 +38,3 @@ * AssetTrash singleton. | ||
}); | ||
this.reset(); | ||
@@ -38,0 +41,0 @@ } |
const vm = require('vm'); | ||
const Asset = require('../Asset'); | ||
const { isWin, parseQuery } = require('../Utils'); | ||
const Options = require('../Options'); | ||
@@ -92,3 +93,3 @@ class ResponsiveLoader { | ||
const contextObject = vm.createContext({ | ||
__webpack_public_path__: Asset.getPublicPath(issuerAssetFile), | ||
__webpack_public_path__: Options.getAssetOutputPath(issuerAssetFile), | ||
module: { exports: {} }, | ||
@@ -112,6 +113,6 @@ }); | ||
if (assets.length === 1) { | ||
asset = Asset.getOutputFile(assets[0], issuerAssetFile); | ||
asset = Options.getAssetOutputFile(assets[0], issuerAssetFile); | ||
} else if (assets.length > 1 && sizes.length > 1) { | ||
asset = assets | ||
.map((assetFile, index) => Asset.getOutputFile(assetFile, issuerAssetFile) + ` ${sizes[index]}w`) | ||
.map((assetFile, index) => Options.getAssetOutputFile(assetFile, issuerAssetFile) + ` ${sizes[index]}w`) | ||
.join(','); | ||
@@ -118,0 +119,0 @@ } |
@@ -20,3 +20,3 @@ const path = require('path'); | ||
filename: '[name].css', | ||
// inline CSS | ||
// inline all CSS | ||
inline: false, | ||
@@ -42,9 +42,10 @@ | ||
* | ||
* @param {array} sourceMaps | ||
* @param {Compilation} compilation | ||
* @param {array} source | ||
* @param {string} assetFile | ||
* @param {Compilation} compilation | ||
* @param {boolean} inline | ||
* @returns {string} | ||
* @private | ||
*/ | ||
extract(sourceMaps, assetFile, compilation) { | ||
extract(compilation, { source: sourceMaps, assetFile, inline }) { | ||
const { compiler } = compilation; | ||
@@ -56,7 +57,6 @@ const { RawSource, ConcatSource } = compiler.webpack.sources; | ||
let contentCSS = ''; | ||
let result = ''; | ||
let contentMapping = ''; | ||
let hasMapping = false; | ||
let mapFile; | ||
let file = ''; | ||
@@ -68,3 +68,3 @@ for (const item of sourceMaps) { | ||
if (contentCSS) contentCSS += '\n'; | ||
if (result) result += '\n'; | ||
@@ -74,12 +74,12 @@ // when in SCSS is used import of CSS file, like `@import url('./style.css');` | ||
if (sourceFile == null && content.endsWith('.css')) { | ||
contentCSS += `@import url(${content});`; | ||
result += `@import url(${content});`; | ||
continue; | ||
} | ||
contentCSS += content; | ||
result += content; | ||
if (sourceMap) { | ||
if (isInlineSourceMap || this.inline) { | ||
if (isInlineSourceMap || inline) { | ||
const sourceURLs = sourceMap.sources | ||
.map((source) => '/*# sourceURL=' + (sourceMap.sourceRoot || '') + source + ' */') | ||
.map((url) => '/*# sourceURL=' + (sourceMap.sourceRoot || '') + url + ' */') | ||
.join('\n'); | ||
@@ -94,12 +94,10 @@ const base64 = Buffer.from(JSON.stringify(sourceMap)).toString('base64'); | ||
} | ||
if (!file && sourceFile) file = sourceFile; | ||
} | ||
if (hasMapping) { | ||
if (isInlineSourceMap || this.inline) { | ||
contentCSS += contentMapping; | ||
if (isInlineSourceMap || inline) { | ||
result += contentMapping; | ||
} else { | ||
mapFile = assetFile + '.map'; | ||
contentCSS += `\n/*# sourceMappingURL=${path.basename(mapFile)} */`; | ||
result += `\n/*# sourceMappingURL=${path.basename(mapFile)} */`; | ||
compilation.emitAsset(mapFile, concatMapping); | ||
@@ -109,3 +107,3 @@ } | ||
return contentCSS; | ||
return result; | ||
}, | ||
@@ -118,3 +116,3 @@ | ||
* @note The content is readonly! | ||
* The CSS content should don't be change, because it has already the compiled source map. | ||
* The CSS content should don't be changed, because it has already the compiled source map. | ||
* | ||
@@ -121,0 +119,0 @@ * @param {string} content The css content generated by css-loader. |
const path = require('path'); | ||
const extractCss = require('./Modules/extractCss'); | ||
const extractCssModule = require('./Modules/extractCss'); | ||
const { isWin, isFunction, pathToPosix } = require('./Utils'); | ||
@@ -51,16 +51,10 @@ const { optionModulesException } = require('./Messages/Exception'); | ||
let extractCssOptions = extractCss(options.css); | ||
const moduleExtractCssOptions = options.modules.find((item) => item.test.source === extractCssOptions.test.source); | ||
const extractCssOptions = extractCssModule(options.css); | ||
if (moduleExtractCssOptions) { | ||
extractCssOptions = moduleExtractCssOptions; | ||
} else { | ||
this.options.modules.unshift(extractCssOptions); | ||
} | ||
if (!options.watchFiles) this.options.watchFiles = {}; | ||
this.options.modules.unshift(extractCssOptions); | ||
this.options.extractJs = options.js || {}; | ||
this.options.extractCss = extractCssOptions; | ||
this.options.afterProcess = isFunction(options.afterProcess) ? options.afterProcess : null; | ||
if (!options.watchFiles) this.options.watchFiles = {}; | ||
} | ||
@@ -75,3 +69,3 @@ | ||
const webpackOutput = options.output; | ||
const { extractJs } = this.options; | ||
const { extractJs, extractCss } = this.options; | ||
@@ -83,10 +77,8 @@ this.webpackOptions = options; | ||
extractJs.verbose = this.toBool(extractJs.verbose, false, false); | ||
extractJs.inline = this.toBool(extractJs.inline, false, false); | ||
extractCss.verbose = this.toBool(extractCss.verbose, false, false); | ||
extractCss.inline = this.toBool(extractCss.inline, false, false); | ||
if (!webpackOutput.path) webpackOutput.path = path.join(this.context, 'dist'); | ||
this.#initWebpackOutput(webpackOutput); | ||
// define js output filename | ||
if (!webpackOutput.filename) { | ||
webpackOutput.filename = '[name].js'; | ||
} | ||
if (extractJs.filename) { | ||
@@ -127,2 +119,34 @@ webpackOutput.filename = extractJs.filename; | ||
static #initWebpackOutput(output) { | ||
let { publicPath } = output; | ||
if (!output.path) output.path = path.join(this.context, 'dist'); | ||
// define js output filename | ||
if (!output.filename) { | ||
output.filename = '[name].js'; | ||
} | ||
if (typeof publicPath === 'function') { | ||
publicPath = publicPath.call(null, {}); | ||
} | ||
if (publicPath === undefined) { | ||
publicPath = 'auto'; | ||
} | ||
// reset initial states | ||
this.isAutoPublicPath = false; | ||
this.isUrlPublicPath = false; | ||
this.isRelativePublicPath = false; | ||
this.webpackPublicPath = publicPath; | ||
this.webpackOutputPath = output.path; | ||
if (publicPath === 'auto') { | ||
this.isAutoPublicPath = true; | ||
} else if (/^(\/\/|https?:\/\/)/i.test(publicPath)) { | ||
this.isUrlPublicPath = true; | ||
} else if (!publicPath.startsWith('/')) { | ||
this.isRelativePublicPath = true; | ||
} | ||
} | ||
/** | ||
@@ -170,6 +194,2 @@ * Entries defined in plugin options are merged with Webpack entry option. | ||
static isProdMode() { | ||
return this.prodMode === true; | ||
} | ||
static isVerbose() { | ||
@@ -188,2 +208,36 @@ return this.verbose; | ||
/** | ||
* Whether the resource should be inlined. | ||
* | ||
* @param {string} resource The resource request with a query. | ||
* @param {boolean} defaultValue When resource query doesn't have the `inline` parameter then return default value. | ||
* @return {boolean} | ||
*/ | ||
static #isInlineResource(resource, defaultValue) { | ||
const [, query] = resource.split('?', 2); | ||
const value = new URLSearchParams(query).get('inline'); | ||
return this.toBool(value, false, defaultValue); | ||
} | ||
/** | ||
* Whether the JS resource should be inlined. | ||
* | ||
* @param {string} resource | ||
* @return {boolean} | ||
*/ | ||
static isInlineJs(resource) { | ||
return this.#isInlineResource(resource, this.options.extractJs.inline); | ||
} | ||
/** | ||
* Whether the CSS resource should be inlined. | ||
* | ||
* @param {string} resource | ||
* @return {boolean} | ||
*/ | ||
static isInlineCss(resource) { | ||
return this.#isInlineResource(resource, this.options.extractCss.inline); | ||
} | ||
/** | ||
* Whether the source file is an entry file. | ||
@@ -200,3 +254,3 @@ * | ||
/** | ||
* Resolve undefined|true|false|'auto' value depend on current Webpack mode dev/prod. | ||
* Resolve undefined|true|false|''|'auto' value depend on current Webpack mode dev/prod. | ||
* | ||
@@ -210,2 +264,5 @@ * @param {boolean|string|undefined} value The value one of true, false, 'auto'. | ||
if (value == null) return defaultValue; | ||
// note: if parameter is defined without a value or value is empty then the value is true | ||
if (value === '' || value === 'true') return true; | ||
if (value === 'false') return false; | ||
if (value === true || value === false) return value; | ||
@@ -249,3 +306,58 @@ | ||
static getWebpackOutputPath() { | ||
return this.webpackOptions.output.path; | ||
} | ||
/** | ||
* Get the output path of the asset. | ||
* | ||
* @param {string | null} assetFile The output asset filename relative by output path. | ||
* @return {string} | ||
*/ | ||
static getAssetOutputPath(assetFile = null) { | ||
let isAutoRelative = assetFile && this.isRelativePublicPath && !assetFile.endsWith('.html'); | ||
if (this.isAutoPublicPath || isAutoRelative) { | ||
if (!assetFile) return ''; | ||
const fullFilename = path.resolve(this.webpackOutputPath, assetFile); | ||
const context = path.dirname(fullFilename); | ||
const publicPath = path.relative(context, this.webpackOutputPath) + '/'; | ||
return isWin ? pathToPosix(publicPath) : publicPath; | ||
} | ||
return this.webpackPublicPath; | ||
} | ||
/** | ||
* Get the output asset file regards the publicPath. | ||
* | ||
* @param {string} assetFile The output asset filename relative by output path. | ||
* @param {string} issuer The output issuer filename relative by output path. | ||
* @return {string} | ||
*/ | ||
static getAssetOutputFile(assetFile, issuer) { | ||
let isAutoRelative = issuer && this.isRelativePublicPath && !issuer.endsWith('.html'); | ||
// if public path is relative, then a resource using not in the template file must be auto resolved | ||
if (this.isAutoPublicPath || isAutoRelative) { | ||
if (!issuer) return assetFile; | ||
const issuerFullFilename = path.resolve(this.webpackOutputPath, issuer); | ||
const context = path.dirname(issuerFullFilename); | ||
const file = path.posix.join(this.webpackOutputPath, assetFile); | ||
const outputFilename = path.relative(context, file); | ||
return isWin ? pathToPosix(outputFilename) : outputFilename; | ||
} | ||
return this.webpackPublicPath + '' + assetFile; | ||
} | ||
/** | ||
* TODO: | ||
* - add dependencies (entry-point => all assets used in the template) as argument | ||
* - test not yet documented experimental feature | ||
* | ||
* @param {string} content | ||
@@ -263,5 +375,2 @@ * @param {string} sourceFile | ||
try { | ||
// TODO: | ||
// - add dependencies (entry-point => all assets used in the template) as argument | ||
// - test not yet documented experimental feature | ||
result = this.options.afterProcess(content, { sourceFile, assetFile }); | ||
@@ -268,0 +377,0 @@ } catch (err) { |
@@ -81,6 +81,6 @@ /** | ||
/** | ||
* Reset states. | ||
* Used for tests to reset state after each test case. | ||
* Called when the compiler is closing. | ||
* Used for tests to reset data after each test case. | ||
*/ | ||
static reset() { | ||
static shutdown() { | ||
this.#used = false; | ||
@@ -87,0 +87,0 @@ this.contextCache.clear(); |
@@ -8,2 +8,3 @@ const path = require('path'); | ||
const { resolveException } = require('./Messages/Exception'); | ||
const Options = require('./Options'); | ||
@@ -45,3 +46,3 @@ class Resolver { | ||
* | ||
* @type {Map<string, {issuers:Map, originalAssetFile:string, moduleHandler?:Function<originalAssetFile:string, issuer:string>}>} | ||
* @type {Map<string, {issuers:Map, originalAssetFile:string, moduleHandler?:(originalAssetFile:string, issuer:string) => string}>} | ||
*/ | ||
@@ -250,15 +251,15 @@ static data = new Map(); | ||
static resolveAsset(sourceFile) { | ||
const { entryPoint, issuerRequest: issuer } = this; | ||
const issuerInline = AssetSource.isInline(issuer); | ||
const item = this.data.get(sourceFile); | ||
let assetOutputFile; | ||
if (!item) return null; | ||
const { entryPoint, issuerRequest, issuerFile: issuer } = this; | ||
const key = this.getAssetKey(issuer, entryPoint); | ||
const assetFile = item.issuers.get(key); | ||
const isInlinedIssuer = AssetSource.isInline(issuerRequest); | ||
if (assetFile && !issuerInline) return assetFile; | ||
if (assetFile && !isInlinedIssuer) return assetFile; | ||
const { originalAssetFile, moduleHandler } = item; | ||
let assetOutputFile; | ||
@@ -270,6 +271,6 @@ if (originalAssetFile != null) { | ||
} else { | ||
const issuerAssetFile = issuerInline ? entryPoint.filename : Asset.findAssetFile(issuer); | ||
const issuerAssetFile = isInlinedIssuer ? entryPoint.filename : Asset.findAssetFile(issuer); | ||
if (issuerAssetFile) { | ||
assetOutputFile = Asset.getOutputFile(originalAssetFile, issuerAssetFile); | ||
assetOutputFile = Options.getAssetOutputFile(originalAssetFile, issuerAssetFile); | ||
} | ||
@@ -276,0 +277,0 @@ } |
const path = require('path'); | ||
const Options = require('./Options'); | ||
const { isWin } = require('../Common/Helpers'); | ||
const { isInline } = require('../Loader/Utils'); | ||
@@ -27,3 +27,3 @@ /** | ||
static add(resource, issuer) { | ||
const [file, query] = resource.split('?', 1); | ||
const [file] = resource.split('?', 1); | ||
@@ -35,4 +35,3 @@ let item = this.files.get(file); | ||
file, | ||
query, | ||
inline: isInline(resource), | ||
inline: Options.isInlineJs(resource), | ||
issuers: [], | ||
@@ -39,0 +38,0 @@ chunkFiles: new Set(), |
Sorry, the diff of this file is too big to display
250974
4518
2485