pug-plugin
Advanced tools
Comparing version 1.1.1 to 1.2.0
# Change log | ||
## 1.2.0 (2022-01-11) | ||
- added support `webpack serve` | ||
- added support require of style source directly in pug, e.g.: | ||
```pug | ||
link(rel='stylesheet' href=require('~Styles/main.scss')) | ||
``` | ||
- added ansi styling by console output | ||
- improve performance | ||
- code optimisation | ||
## 1.1.1 (2021-12-10) | ||
- update pug-loader: fixed path issues on windows | ||
- update pug-loader: fixed path issues on Windows | ||
@@ -6,0 +16,0 @@ ## 1.1.0 (2021-12-07) |
{ | ||
"name": "pug-plugin", | ||
"version": "1.1.1", | ||
"description": "The pug plugin for webpack to handle pug files from webpack entries and save them as HTML.", | ||
"version": "1.2.0", | ||
"description": "The pug plugin to handle the pug, html, css, scss files in webpack entry and extract css from pug.", | ||
"keywords": [ | ||
@@ -9,9 +9,14 @@ "pug", | ||
"template", | ||
"css", | ||
"scss", | ||
"styles", | ||
"extract", | ||
"webpack", | ||
"entry", | ||
"pug-loader", | ||
"assets", | ||
"resource", | ||
"plugin", | ||
"loader", | ||
"pug-plugin", | ||
"html-loader", | ||
"HtmlWebpackPlugin" | ||
"pug-loader" | ||
], | ||
@@ -57,18 +62,21 @@ "license": "ISC", | ||
"dependencies": { | ||
"@webdiscus/pug-loader": "^1.5.1", | ||
"@webdiscus/pug-loader": "^1.6.0-alpha1", | ||
"ansis": "^1.3.2", | ||
"webpack-merge": "^5.8.0" | ||
}, | ||
"devDependencies": { | ||
"@babel/core": "^7.16.0", | ||
"@babel/preset-env": "^7.16.4", | ||
"@types/jest": "^27.0.3", | ||
"@babel/core": "^7.16.7", | ||
"@babel/preset-env": "^7.16.8", | ||
"@types/jest": "^27.4.0", | ||
"css-loader": "^6.5.1", | ||
"html-loader": "^3.0.1", | ||
"jest": "^27.4.3", | ||
"html-loader": "^3.1.0", | ||
"jest": "^27.4.7", | ||
"mini-css-extract-plugin": "^2.4.6", | ||
"prettier": "^2.5.1", | ||
"rimraf": "^3.0.2", | ||
"sass": "^1.44.0", | ||
"sass-loader": "^12.3.0", | ||
"webpack": "^5.64.4" | ||
"sass": "^1.47.0", | ||
"sass-loader": "^12.4.0", | ||
"style-loader": "^3.3.1", | ||
"webpack": "^5.65.0" | ||
} | ||
} |
161
README.md
@@ -1,2 +0,2 @@ | ||
[![npm version](https://badge.fury.io/js/pug-plugin.svg)](https://badge.fury.io/js/pug-plugin) | ||
[![npm](https://img.shields.io/npm/v/pug-plugin?logo=npm&color=brightgreen "npm package")](https://www.npmjs.com/package/pug-plugin "download npm package") | ||
[![node](https://img.shields.io/node/v/pug-plugin)](https://nodejs.org) | ||
@@ -10,3 +10,4 @@ [![node](https://img.shields.io/github/package-json/dependency-version/webdiscus/pug-plugin/peer/webpack)](https://webpack.js.org/) | ||
Webpack plugin for the [Pug](https://pugjs.org) templates.\ | ||
This plugin extract HTML and CSS from `pug` `html` `scss` `css` files defined by `webpack entry` into output directory. | ||
This plugin extract HTML and CSS from `pug` `html` `scss` `css` files defined by `webpack entry` | ||
and dynamically replace in a template the required source filename of CSS, image with a public hashed name. | ||
@@ -17,3 +18,4 @@ Using the `pug-plugin` and `pug` `html` `scss` `css` assets in the `webpack entry` no longer requires additional plugins such as: | ||
- [webpack-remove-empty-scripts](https://github.com/webdiscus/webpack-remove-empty-scripts) | ||
or [webpack-fix-style-only-entries](https://github.com/fqborges/webpack-fix-style-only-entries) (bug fix plugins for `mini-css-extract-plugin`) | ||
or [webpack-fix-style-only-entries](https://github.com/fqborges/webpack-fix-style-only-entries) \ | ||
(bug fix plugins for `mini-css-extract-plugin`) | ||
- [pug-loader](https://github.com/webdiscus/pug-loader) (this loader is already included in the `pug-plugin`) | ||
@@ -26,6 +28,39 @@ | ||
```console | ||
```bash | ||
npm install pug-plugin --save-dev | ||
``` | ||
## Quick Start | ||
The minimal configuration in `webpack.config.js`: | ||
```js | ||
const path = require('path'); | ||
const PugPlugin = require('pug-plugin'); | ||
module.exports = { | ||
output: { | ||
path: path.join(__dirname, 'public/'), | ||
publicPath: '/', // must be defined any path, `auto` is not supported | ||
}, | ||
entry: { | ||
index: './src/pages/index.pug', // ==> public/index.html | ||
}, | ||
plugins: [ | ||
new PugPlugin(), // add the plugin | ||
], | ||
module: { | ||
rules: [ | ||
{ | ||
test: /\.pug$/, | ||
loader: PugPlugin.loader, // add the pug-loader | ||
}, | ||
], | ||
}, | ||
}; | ||
``` | ||
> **Note**: this configuration work without `html-webpack-plugin`. | ||
## Motivation | ||
@@ -139,3 +174,3 @@ | ||
outputPath: path.join(__dirname, 'public/assets/css/'), | ||
filename: isProduction ? '[name][contenthash:8].css' : '[name].css' | ||
filename: isProduction ? '[name].[contenthash:8].css' : '[name].css' | ||
}, | ||
@@ -192,2 +227,11 @@ ], | ||
``` | ||
- extract CSS via `require()` directly in pug and replace the source filename with a public hashed name. In this case is no need to define the style in the webpack entry: | ||
```pug | ||
link(rel='stylesheet' href=require('~Styles/main.scss')) | ||
``` | ||
output | ||
```html | ||
<link rel="stylesheet" href="/assets/css/main.6f4d012e.css"> | ||
``` | ||
[see complete example of usage](#require-style) | ||
@@ -337,11 +381,102 @@ <a id="options" name="options" href="#options"></a> | ||
### Extract CSS file from SASS | ||
<a id="require-style" name="require-style" href="#require-style"></a> | ||
### Extract CSS via `require` in pug | ||
Dependencies: | ||
- `css-loader` this loader translates CSS into JS strings | ||
- `sass-loader` need to handle the `.scss` file type | ||
- `sass` needed for `sass-loader` to compile Sass CSS | ||
Dependencies: | ||
- `css-loader` handles `.css` files and prepare CSS for any CSS extractor | ||
- `sass-loader` handles `.scss` files | ||
- `sass` compiles Sass to CSS | ||
Install: `npm install css-loader sass sass-loader --save-dev` | ||
In this case no to need define the style in webpack entry. | ||
The CSS will be extracted from a style source required directly in the pug template. | ||
The pug template `src/templates/index.pug`: | ||
```pug | ||
html | ||
head | ||
link(href=require('~Styles/my-style.scss')) | ||
body | ||
p Hello World! | ||
``` | ||
Add to the `webpack.config.js` following: | ||
```js | ||
module.exports = { | ||
entry: { | ||
// ... | ||
index: 'src/templates/index.pug', // the pug file with required style | ||
}, | ||
plugins: [ | ||
// ... | ||
// handle pug and style files defined in webpack.entry | ||
new PugPlugin({ | ||
modules: [ | ||
PugPlugin.extractCss(), // enable CSS extraction | ||
], | ||
}), | ||
], | ||
module: { | ||
rules: [ | ||
// ... | ||
{ | ||
test: /\.pug$/, | ||
loader: PugPlugin.loader, // the pug-loader is already included in the PugPlugin | ||
}, | ||
{ | ||
test: /\.(css|sass|scss)$/, | ||
type: 'asset/resource', // extract css in pug via require | ||
generator: { | ||
filename: 'assets/css/[name].[hash:8].css', // save extracted css in public path | ||
}, | ||
use: [ 'css-loader', 'sass-loader' ], // extract css from a style source | ||
} | ||
], | ||
}, | ||
} | ||
``` | ||
The generated HTML: | ||
```html | ||
<html> | ||
<head> | ||
<link rel="stylesheet" href="/assets/css/main.f57966f4.css"> | ||
</head> | ||
<body> | ||
<p>Hello World!</p> | ||
</body> | ||
</html> | ||
``` | ||
> **Note**: don't needed any additional plugin, like `mini-css-extract-plugin`. | ||
## ! ACHTUNG ! ATTENTION ! | ||
### STOP import styles in JavaScript! This is very BAD praxis. | ||
**DON'T DO IT :** `import ./src/styles.scss` This is popular but `dirty way`! | ||
### Clarification | ||
The importing of styles in JavaScript triggers the events in Webpack which call the `mini-css-extract-plugin` loader | ||
to extract CSS from imported style source. Then the `html-webpack-plugin` using a magic add the `<link href="styles.css">` with filename of extracted CSS to any HTML file in head at last position. | ||
Your can't define in which HTML file will be added style and in which order. You are not in control of this process! | ||
This process requires two different plugins and has poor performance.\ | ||
The single `pug-plugin` does it with right way, in one step and much faster. | ||
> ### Correct ways | ||
> - add a source style file directly in pug via `require`, like `link(href=require('~Styles/styles.scss'))` | ||
> - add a compiled css file directly in pug, like `link(href='/assets/styles.css')` and add the source of the style in webpack entry.\ | ||
> Yes, in this case may be needed additional assets manifest plugin to replace original filename with hashed name. But this is the right way.\ | ||
> In the future, will be added support to automatically replace the asset file name with a hashed one. | ||
### Extract CSS from SASS defined in webpack entry | ||
Dependencies: | ||
- `css-loader` handles `.css` files and prepare CSS for any CSS extractor | ||
- `sass-loader` handles `.scss` files | ||
- `sass` compiles Sass to CSS | ||
Install: `npm install css-loader sass sass-loader --save-dev` | ||
webpack.config.js | ||
@@ -363,6 +498,6 @@ ```js | ||
modules: [ | ||
// add the module to extract CSS into output file | ||
// add the module to extract CSS | ||
// see options https://github.com/webdiscus/pug-plugin#options | ||
PugPlugin.extractCss({ | ||
filename: isProduction ? '[name][contenthash:8].css' : '[name].css', | ||
filename: isProduction ? '[name].[contenthash:8].css' : '[name].css', | ||
}) | ||
@@ -464,3 +599,3 @@ ], | ||
PugPlugin.extractCss({ | ||
filename: isProduction ? '[name][contenthash:8].css' : '[name].css', | ||
filename: isProduction ? '[name].[contenthash:8].css' : '[name].css', | ||
sourcePath: 'src/assets/sass/', | ||
@@ -467,0 +602,0 @@ outputPath: 'assets/css/', |
412
src/index.js
@@ -1,7 +0,5 @@ | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const ansis = require('ansis'); | ||
const { merge } = require('webpack-merge'); | ||
const colstr = require('./color-string'); | ||
const { plugin, isFunction, shallowEqual } = require('./utils'); | ||
const { plugin, isFunction, requireResource } = require('./utils'); | ||
const { extractHtml, extractCss } = require('./modules'); | ||
@@ -40,7 +38,7 @@ | ||
* and save the file relative by output path, defined in webpack.options.output.path. | ||
* @property {string | function(PathData, AssetInfo): string} filenameTemplate The filename template. | ||
* @property {string | function(PathData, AssetInfo): string} filenameTemplate The filename template or function. | ||
* @property {string} filename The asset filename. | ||
* The template strings support only this substitutions: [name], [base], [path], [ext], [id], [contenthash], [contenthash:nn] | ||
* See https://webpack.js.org/configuration/output/#outputfilename | ||
* @property {string} import | ||
* @property {string} importFile | ||
* @property {string} outputPath | ||
@@ -50,8 +48,17 @@ * @property {string} sourcePath | ||
* See https://webpack.js.org/configuration/output/#outputlibrary | ||
* @property {function(string, AssetEntry, Compilation): string} [postprocess = null] The post process for extracted content from entry. | ||
* @property {function(string, AssetInfo, Compilation): string} [postprocess = null] The post process for extracted content from entry. | ||
* @property {Array} resources | ||
* @property {boolean} [verbose = false] Show an information by handles of the entry in a postprocess. | ||
* @property {RawSource} RawSource The reference of the class compiler.webpack.sources.RawSource. | ||
*/ | ||
/** | ||
* @typedef {Object} EntryAssetInfo | ||
* @property {boolean} isEntry True if is the asset from entry, false if asset is required from pug. | ||
* @property {string} entryFile The entry file. | ||
* @property {string | (function(PathData, AssetInfo): string)} filename The filename template or function. | ||
* @property {string} sourceFile The source file. | ||
* @property {string} assetFile The output asset file with relative path by webpack output path. | ||
*/ | ||
/** | ||
* @var {PugPluginOptions} defaultOptions | ||
@@ -68,6 +75,8 @@ */ | ||
// experimental, reserved feature for the future: each entry has its own local options that override global options | ||
// each entry has its own local options that override global options | ||
modules: [], | ||
}; | ||
const MODULE_TYPE = `${plugin}/entry`; | ||
class PugPlugin { | ||
@@ -77,2 +86,8 @@ /** @type {AssetEntry[]} */ | ||
/** | ||
* Resources required in the template. | ||
* @type {[]} | ||
*/ | ||
entryAssets = []; | ||
entryLibrary = { | ||
@@ -91,12 +106,6 @@ name: 'return', | ||
this.verbose = this.options.verbose; | ||
} | ||
/** | ||
* Get the entry by filename. | ||
* | ||
* @param {string} filename The filename is a key of the compilation assets object. | ||
* @return {AssetEntry} | ||
*/ | ||
getEntry(filename) { | ||
return this.entries.find((entry) => entry.filename === filename); | ||
if (options.modules && !Array.isArray(options.modules)) { | ||
this.optionModulesException(options.modules); | ||
} | ||
} | ||
@@ -107,2 +116,10 @@ | ||
const webpackOutputPath = compiler.options.output.path; | ||
const webpackOutputPublicPath = compiler.options.output.publicPath; | ||
const { RawSource } = compiler.webpack.sources; | ||
// TODO resolve 'auto' publicPath | ||
if (webpackOutputPublicPath === 'auto') this.publicPathException(); | ||
requireResource.publicPath = webpackOutputPublicPath; | ||
// enable library type `jsonp` for compilation JS from source into HTML string via Function() | ||
@@ -113,5 +130,11 @@ if (compiler.options.output.enabledLibraryTypes.indexOf('jsonp') < 0) { | ||
// TODO prevent warning split chunk 240 KB | ||
// const { splitChunks } = compiler.options.optimization; | ||
// if (splitChunks) { | ||
// if (splitChunks.defaultSizeTypes.includes('...')) { | ||
// splitChunks.defaultSizeTypes.push(MODULE_TYPE); | ||
// } | ||
// } | ||
compiler.hooks.entryOption.tap(plugin, (context, entries) => { | ||
const webpackOutputPath = compiler.options.output.path; | ||
if (!this.options.sourcePath) this.options.sourcePath = compiler.options.context; | ||
@@ -131,3 +154,3 @@ if (!this.options.outputPath) this.options.outputPath = webpackOutputPath; | ||
let sourceFile = entry.import[0]; | ||
const module = this.options.modules.find((module) => module.enabled !== false && module.test.test(sourceFile)); | ||
const module = this.getModule(sourceFile); | ||
@@ -160,3 +183,3 @@ if (!extensionRegexp.test(sourceFile) && !module) continue; | ||
assetFile: undefined, | ||
import: sourceFile, | ||
importFile: sourceFile, | ||
sourcePath: sourcePath, | ||
@@ -166,4 +189,3 @@ outputPath: outputPath, | ||
postprocess: isFunction(postprocess) ? postprocess : null, | ||
verbose: verbose, | ||
RawSource: compiler.webpack.sources.RawSource, | ||
verbose, | ||
}; | ||
@@ -173,3 +195,3 @@ | ||
// define lazy memoized getter for the property `filename`, it will be generated later | ||
if (!assetEntry.filename) | ||
if (!assetEntry.filename) { | ||
Object.defineProperty(assetEntry, 'filename', { | ||
@@ -181,5 +203,7 @@ get() { | ||
delete this.filename; | ||
return (this.filename = filename); | ||
}, | ||
}); | ||
} | ||
@@ -193,47 +217,133 @@ return isFunction(filenameTemplate) ? filenameTemplate(pathData, assetInfo) : filenameTemplate; | ||
compiler.hooks.compilation.tap(plugin, (compilation) => { | ||
let { verbose } = this.options; | ||
compiler.hooks.thisCompilation.tap(plugin, (compilation) => { | ||
const verbose = this.verbose; | ||
// todo resolve 'auto' publicPath | ||
if (compilation.outputOptions.publicPath === 'auto') this.publicPathException(); | ||
compilation.hooks.buildModule.tap(plugin, (module) => { | ||
if (this.isChunkModuleInEntry(module)) { | ||
module.type = MODULE_TYPE; | ||
} | ||
}); | ||
// render source code | ||
compilation.hooks.renderManifest.tap(plugin, (result, { chunk }) => { | ||
const { chunkGraph } = compilation; | ||
const { HotUpdateChunk } = compiler.webpack; | ||
const filenameTemplate = chunk.filenameTemplate; | ||
let source = '', | ||
compiledResult = ''; | ||
// don't hot update chunks | ||
// TODO research whether the chunk can be the instance of HotUpdateChunk | ||
if (chunk instanceof HotUpdateChunk) return; | ||
const entry = this.getEntryByName(chunk.name); | ||
// process only entries supported by this plugin | ||
if (!entry) return; | ||
requireResource.resources = {}; | ||
for (const module of chunkGraph.getChunkModules(chunk)) { | ||
if (module.type === 'asset/resource') { | ||
requireResource.resources[module.resource] = module.buildInfo.filename; | ||
this.entryAssets.push({ | ||
entryFile: entry.importFile, | ||
sourceFile: module.resource, | ||
assetFile: module.buildInfo.filename, | ||
}); | ||
} else if (module.type === MODULE_TYPE) { | ||
source = module.originalSource().source().toString(); | ||
source = this.getFunctionCode(source); | ||
} | ||
} | ||
if (~source.indexOf('___CSS_LOADER_')) { | ||
compiledResult = this.extractCss(source, { sourceFile: entry.importFile, moduleId: chunk.id }); | ||
} else { | ||
compiledResult = this.extractHtml(source, entry); | ||
} | ||
result.push({ | ||
render: () => new RawSource(compiledResult), | ||
filenameTemplate, | ||
pathOptions: { chunk, contentHashType: 'javascript' }, | ||
identifier: `${plugin}.${chunk.id}`, | ||
hash: chunk.contentHash['javascript'], | ||
}); | ||
}); | ||
// | ||
compilation.hooks.chunkAsset.tap(plugin, (chunk, filename) => { | ||
// TODO for @next release | ||
// - collect entry files for manifest to replace original name with hashed | ||
//const asset = compilation.getAsset(filename); | ||
//const assetInfo = compilation.assetsInfo.get(filename) || {}; | ||
}); | ||
// only here can be an asset deleted or emitted | ||
compilation.hooks.processAssets.tap( | ||
{ | ||
name: plugin, | ||
// run this process before all others | ||
stage: compilation.PROCESS_ASSETS_STAGE_ADDITIONAL - 1000, | ||
stage: compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE, | ||
}, | ||
(assets) => { | ||
let filename, result; | ||
/** @type {null|string|object|function} result */ | ||
let filename; | ||
for (filename in assets) { | ||
const entry = this.getEntry(filename); | ||
const entry = this.getEntryByFilename(filename); | ||
let entryAsset, module, assetFile, source, result, postprocess; | ||
if (!entry) continue; | ||
if (verbose) this.verboseEntry(entry); | ||
if (entry) { | ||
// the asset defined in webpack entry | ||
if (verbose) this.verboseEntry(entry); | ||
assetFile = entry.assetFile; | ||
postprocess = entry.postprocess; | ||
// the result of rendering in the `renderManifest` hook | ||
result = assets[filename].source(); | ||
} else { | ||
// the asset required in pug | ||
module = this.getModule(filename); | ||
if (!module) continue; | ||
postprocess = module.postprocess; | ||
const source = assets[filename].source(); | ||
// generates the html string from source code | ||
result = new Function('', source)(); | ||
if (result == null) this.entryException(entry); | ||
// extract required resource which is not presents in webpack entry | ||
entryAsset = this.entryAssets.find((item) => item.assetFile === filename); | ||
if (!entryAsset) { | ||
// remove double assets from webpack entry processed via `asset/resource` | ||
compilation.deleteAsset(filename); | ||
continue; | ||
} | ||
// detect result of ES module | ||
if (result.default) result = result.default; | ||
assetFile = filename; | ||
source = assets[filename].source().toString(); | ||
source = this.getFunctionCode(source); | ||
if (isFunction(result)) { | ||
try { | ||
// pug-loader.method: compile | ||
// note: all external variables are already assigned to locals in the template function via pug-loader, | ||
// the argument must be empty object to avoid error by reading property from undefined | ||
result = result({}); | ||
} catch (error) { | ||
this.executeAssetSourceException(error, entry); | ||
if (~source.indexOf('___CSS_LOADER_')) { | ||
const importFile = path.join(webpackOutputPath, filename); | ||
if (verbose) { | ||
entryAsset.assetFile = path.join(webpackOutputPath, assetFile); | ||
this.verboseExtractAsset(entryAsset); | ||
} | ||
result = this.extractCss(source, { sourceFile: importFile, moduleId: filename }); | ||
} | ||
} | ||
if (entry.postprocess) { | ||
const info = { | ||
isEntry: !!entry, | ||
entryFile: entry ? entry.file : entryAsset.entryFile, | ||
filename: entry ? entry.filenameTemplate : module.filename, | ||
sourceFile: entry ? entry.importFile : entryAsset.sourceFile, | ||
assetFile, | ||
}; | ||
if (result == null) { | ||
this.entryException('The extract from source is undefined.', source, info); | ||
} | ||
if (postprocess) { | ||
try { | ||
result = entry.postprocess(result, entry, compilation); | ||
result = postprocess(result, info, compilation); | ||
} catch (error) { | ||
this.postprocessException(error, entry); | ||
this.postprocessException(error, info); | ||
} | ||
@@ -245,3 +355,3 @@ } | ||
compilation.deleteAsset(filename); | ||
compilation.emitAsset(entry.assetFile, new entry.RawSource(result, false)); | ||
compilation.emitAsset(assetFile, new RawSource(result, false)); | ||
} | ||
@@ -255,19 +365,142 @@ } | ||
/** | ||
* Extract the html from source code. | ||
* | ||
* @param {string} source | ||
* @param {AssetEntry} entry | ||
* @returns {string} | ||
*/ | ||
extractHtml(source, entry) { | ||
let result = new Function('require', source)(requireResource); | ||
if (isFunction(result)) { | ||
try { | ||
return result(); | ||
} catch (error) { | ||
this.executeAssetSourceException(error, entry); | ||
} | ||
} | ||
return result; | ||
} | ||
/** | ||
* Extract the css and source map from code generated by`css-loader`. | ||
* | ||
* @param {string} source The source generated by `css-loader`. | ||
* @param {string} sourceFile The full path of source file. | ||
* @param {string} moduleId The id of the css module. | ||
* @returns {string} | ||
*/ | ||
extractCss(source, { sourceFile, moduleId }) { | ||
// fix path in `require()` by `css-loader` options: { esModule: false } | ||
// replace `import from` with `require()` by `css-loader` options: { esModule: true } | ||
const matches = ~source.indexOf('require(') | ||
? source.matchAll(/.+?(\w+) = require\("(.+)"\);/g) | ||
: source.matchAll(/import (.+) from "(.+)";/g); | ||
for (const [match, variable, file] of matches) { | ||
const fullPath = path.join(path.dirname(sourceFile), file); | ||
const relPath = path.relative(__dirname, fullPath); | ||
const replace = `var ${variable} = require('${relPath}');`; | ||
source = source.replace(match, replace); | ||
} | ||
// generate the export object of the `css-loader` from source | ||
// this object has the own method `toString()` for concatenation of all source strings | ||
// see node_modules/css-loader/dist/runtime/sourceMaps.js | ||
const result = new Function('require, module', source)(require, { id: moduleId }); | ||
return result.toString(); | ||
} | ||
/** | ||
* @param {string} filename The filename is a key of the compilation assets object. | ||
* @return {AssetEntry} | ||
*/ | ||
getEntryByFilename(filename) { | ||
return this.entries.find((entry) => entry.filename === filename); | ||
} | ||
/** | ||
* @param {string} name The entry name. | ||
* @returns {AssetEntry} | ||
*/ | ||
getEntryByName(name) { | ||
return this.entries.find((entry) => entry.name === name); | ||
} | ||
/** | ||
* @param {string} filename | ||
* @returns {ModuleOptions} | ||
*/ | ||
getModule(filename) { | ||
return this.options.modules.find((module) => module.enabled !== false && module.test.test(filename)); | ||
} | ||
/** | ||
* @param {Object} module The webpack chunk module. | ||
* @returns {boolean} | ||
*/ | ||
isChunkModuleInEntry(module) { | ||
const importFile = module.resource; | ||
return importFile ? this.entries.find((entry) => entry.importFile === importFile) !== undefined : false; | ||
} | ||
/** | ||
* Transform source of module to function code. | ||
* @param {string} source | ||
* @returns {string} | ||
*/ | ||
getFunctionCode(source) { | ||
if (~source.indexOf('export default')) { | ||
source = source.replace('export default', `return`); | ||
} else if (~source.indexOf('module.exports')) { | ||
source = source.replace(/module.exports\s*=/, `return `); | ||
} | ||
return source; | ||
} | ||
/** | ||
* @param {AssetEntry} entry | ||
*/ | ||
verboseEntry(entry) { | ||
if (!entry) return; | ||
if (!this.hasVerboseOut) console.log('\n'); | ||
this.hasVerboseOut = true; | ||
console.log( | ||
`[${colstr.yellow(plugin)}] Compile the entry ${colstr.green(entry.name)}\n` + | ||
` - filename: ${typeof entry.filename === 'function' ? colstr.cyan('[Function]') : entry.filename}\n` + | ||
` - import: ${entry.import}\n` + | ||
` - output: ${entry.file}\n` | ||
`${ansis.black.bgYellow(`[${plugin}]`)} Compile the entry ${ansis.green(entry.name)}\n` + | ||
` - filename: ${ | ||
typeof entry.filename === 'function' ? ansis.cyan('[Function]') : ansis.magenta(entry.filenameTemplate) | ||
}\n` + | ||
` - source: ${ansis.cyan(entry.importFile)}\n` + | ||
` - output: ${ansis.cyanBright(entry.file)}\n` | ||
); | ||
} | ||
publicPathException() { | ||
/** | ||
* @param {string} entryFile | ||
* @param {string} sourceFile | ||
* @param {string} assetFile | ||
*/ | ||
verboseExtractAsset({ entryFile, sourceFile, assetFile }) { | ||
if (!this.hasVerboseOut) console.log('\n'); | ||
this.hasVerboseOut = true; | ||
console.log( | ||
`${ansis.black.bgYellow(`[${plugin}]`)} Extract the asset from ${ansis.green(entryFile)}\n` + | ||
` - source: ${ansis.cyan(sourceFile)}\n` + | ||
` - output: ${ansis.cyanBright(assetFile)}\n` | ||
); | ||
} | ||
/** | ||
* @param {ModuleOptions[]} modules | ||
* @throws {Error} | ||
*/ | ||
optionModulesException(modules) { | ||
throw new Error( | ||
`\n[${plugin}] This plugin yet not support 'auto' publicPath.\n` + | ||
`Define a publicPath in the webpack configuration, e.g. output: { publicPath: '/' }.\n` | ||
`\n${ansis.black.bgRedBright(`[${plugin}]`)} The plugin option ${ansis.green( | ||
'modules' | ||
)} must be the array of ${ansis.green('ModuleOptions')} but given:\n` + | ||
ansis.cyanBright(JSON.stringify(modules)) + | ||
`\n` | ||
); | ||
@@ -277,18 +510,8 @@ } | ||
/** | ||
* @param {AssetEntry} entry | ||
* @throws {Error} | ||
*/ | ||
entryException(entry) { | ||
const existFile = fs.existsSync(entry.import); | ||
publicPathException() { | ||
throw new Error( | ||
`\n[${plugin}] The compiled source from the file "${entry.filename}" is undefined.` + | ||
'\n' + | ||
`Possible reasons: \n` + | ||
(!existFile ? ` - the import file '${entry.import}' is not found\n` : '') + | ||
(existFile ? ` - the compiled file '${entry.file}' is not executable JavaScript\n` : '') + | ||
(entry.library && !shallowEqual(entry.library, this.entryLibrary) | ||
? ` - the library must be undefined or exact ${JSON.stringify(this.entryLibrary)}, but given ${ | ||
entry.library | ||
}\n` | ||
: '') + | ||
'\n' | ||
`\n${ansis.black.bgRedBright(`[${plugin}]`)} This plugin yet not support 'auto' publicPath.\n` + | ||
`Define a publicPath in the webpack configuration, e.g. output: { publicPath: '/' }.\n` | ||
); | ||
@@ -298,9 +521,37 @@ } | ||
/** | ||
* @param {string} errorMessage | ||
* @param {string} source | ||
* @param {EntryAssetInfo} info | ||
* @throws {Error} | ||
*/ | ||
entryException(errorMessage, source, info) { | ||
let error = | ||
`\n${ansis.black.bgRedBright(`[${plugin}]`)} Fail by entry file ${ansis.cyan(info.assetFile)}. ` + | ||
errorMessage + | ||
'\n' + | ||
ansis.yellow(`Possible reasons:`) + | ||
'\n'; | ||
const [unresolvedRequire] = /(<[^>]+require\(.+\).+?>)/.exec(source) || []; | ||
if (unresolvedRequire) { | ||
error += ` - the extracted JavaScript contain not resolved ${ansis.cyanBright('require()')}:\n`; | ||
error += ansis.magenta(unresolvedRequire); | ||
} else { | ||
error += ` - the extracted JavaScript is not executable:\n`; | ||
error += ansis.cyanBright(source); | ||
error += '\n'; | ||
} | ||
throw new Error(ansis.white(error) + '\n'); | ||
} | ||
/** | ||
* @param {Error} error | ||
* @param {AssetEntry} entry | ||
* @throws {Error} | ||
*/ | ||
executeAssetSourceException(error, entry) { | ||
throw new Error( | ||
`\n[${plugin}] Asset source execution failed by the entry '${entry.name}'.\n` + | ||
`The file '${entry.import}'.\n` + | ||
`\n${ansis.black.bgRedBright(`[${plugin}]`)} Asset source execution failed by the entry '${entry.name}'.\n` + | ||
`The file '${entry.importFile}'.\n` + | ||
error | ||
@@ -312,8 +563,9 @@ ); | ||
* @param {Error} error | ||
* @param {AssetEntry} entry | ||
* @param {EntryAssetInfo} info | ||
* @throws {Error} | ||
*/ | ||
postprocessException(error, entry) { | ||
postprocessException(error, info) { | ||
throw new Error( | ||
`\n[${plugin}] Postprocess execution failed by the entry '${entry.name}'.\n` + | ||
`The file '${entry.import}'.\n` + | ||
`\n${ansis.black.bgRedBright(`[${plugin}]`)} Postprocess execution failed by the entry '${info.entryFile}'.\n` + | ||
`The source file '${info.sourceFile}'.\n` + | ||
error | ||
@@ -320,0 +572,0 @@ ); |
@@ -1,5 +0,4 @@ | ||
const path = require('path'); | ||
const { merge } = require('webpack-merge'); | ||
const { plugin } = require('./utils'); | ||
const colstr = require('./color-string'); | ||
const ansis = require('ansis'); | ||
@@ -29,3 +28,3 @@ /** | ||
/** | ||
* @param {string} content | ||
* @param {string} content The extracted html. | ||
* @param {AssetEntry} entry | ||
@@ -46,3 +45,3 @@ * @param {Compilation} compilation | ||
/** | ||
* The lightweight plugin module to extract the css and source map from asset content. | ||
* The lightweight plugin module to extract the css from asset content. | ||
* todo test cases: | ||
@@ -66,3 +65,3 @@ * - add supports a chunk name template with [id] | ||
/** | ||
* @param {array} content The result from css-loader. | ||
* @param {string} content The css content generated by css-loader. | ||
* @param {AssetEntry} entry | ||
@@ -73,23 +72,8 @@ * @param {Compilation} compilation | ||
postprocess: (content, entry, compilation) => { | ||
const [item] = content; | ||
const [sourceFile, source, nop, sourceMap] = item; | ||
let sourceMappingURL = ''; | ||
if (entry.verbose) | ||
if (entry.verbose) { | ||
console.log( | ||
colstr.bgYellow(`[${plugin}]`, colstr.colors.black) + | ||
colstr.bgGreen(` Extract CSS `, colstr.colors.black) + | ||
colstr.cyan(' ' + entry.file) | ||
ansis.black.bgYellow(`[${plugin}]`) + ansis.black.bgGreen(` Extract CSS `) + ansis.cyan(' ' + entry.file) | ||
); | ||
// add css source map for development mode | ||
if (compilation.options.devtool) { | ||
const rawSourceMap = new entry.RawSource(JSON.stringify(sourceMap)); | ||
const mapFile = entry.assetFile + '.map'; | ||
compilation.emitAsset(mapFile, rawSourceMap); | ||
sourceMappingURL = `\n/*# sourceMappingURL=${path.basename(mapFile)} */`; | ||
} | ||
return source + sourceMappingURL; | ||
return content; | ||
}, | ||
@@ -96,0 +80,0 @@ }; |
@@ -0,1 +1,2 @@ | ||
const path = require('path'); | ||
const plugin = 'pug-plugin'; | ||
@@ -6,2 +7,28 @@ | ||
/** | ||
* Require the resource file from source in pug template. | ||
* | ||
* @param {string} file The required file from source directory. | ||
* @returns {string} The output asset filename generated by filename template. | ||
*/ | ||
const requireResource = (file) => { | ||
const self = requireResource; | ||
// normalize the path, e.g.: 'path/to/../to/file' => 'path/to/file' | ||
const normalizedFile = path.join(file); | ||
const assetFile = self.resources[normalizedFile]; | ||
return path.join(self.publicPath, assetFile); | ||
}; | ||
/** | ||
* @type {string} The the output public path is `webpack.options.output.publicPath`. | ||
*/ | ||
requireResource.publicPath = ''; | ||
/** | ||
* The mapping of the source file to the generated asset file. | ||
* @type {{key: string}} | ||
*/ | ||
requireResource.resources = {}; | ||
/** | ||
* Simple equal of two objects. | ||
@@ -26,65 +53,7 @@ * | ||
//const AUTO_PUBLIC_PATH = '__pug_plugin_public_path_auto__'; | ||
//const ABSOLUTE_PUBLIC_PATH = 'webpack:///pug-plugin/'; | ||
//const SINGLE_DOT_PATH_SEGMENT = '__pug_plugin_single_dot_path_segment__'; | ||
// todo Implement 'auto' publicPath, following is just research code from mini-css-extract-plugin: | ||
/*const getPublicPath = (compilation) => { | ||
let { publicPath } = compilation.outputOptions || { publicPath: '' }; | ||
//if (typeof options.publicPath === 'string') { | ||
// publicPath = options.publicPath; | ||
//} else if (isFunction(options.publicPath)) { | ||
// publicPath = options.publicPath(this.resourcePath, this.rootContext); | ||
//} | ||
if (publicPath === 'auto') { | ||
publicPath = AUTO_PUBLIC_PATH; | ||
} | ||
const isAbsolutePublicPath = /^[a-zA-Z][a-zA-Z\d+\-.]*?:/.test(publicPath); | ||
const publicPathForExtract = isAbsolutePublicPath | ||
? publicPath | ||
: `${ABSOLUTE_PUBLIC_PATH}${publicPath.replace(/\./g, SINGLE_DOT_PATH_SEGMENT)}`; | ||
return publicPathForExtract; | ||
};*/ | ||
// todo Resolve 'auto' publicPath into real path, following is just research code from mini-css-extract-plugin: | ||
/*function resolvePublicPath(filename, outputPath, enforceRelative) { | ||
let depth = -1; | ||
let append = ''; | ||
outputPath = outputPath.replace(/[\\/]$/, ''); | ||
for (const part of filename.split(/[/\\]+/)) { | ||
if (part === '..') { | ||
if (depth > -1) { | ||
depth--; | ||
} else { | ||
const i = outputPath.lastIndexOf('/'); | ||
const j = outputPath.lastIndexOf('\\'); | ||
const pos = i < 0 ? j : j < 0 ? i : Math.max(i, j); | ||
if (pos < 0) return `${outputPath}/`; | ||
append = `${outputPath.slice(pos + 1)}/${append}`; | ||
outputPath = outputPath.slice(0, pos); | ||
} | ||
} else if (part !== '.') { | ||
depth++; | ||
} | ||
} | ||
return depth > 0 ? `${'../'.repeat(depth)}${append}` : enforceRelative ? `./${append}` : append; | ||
}*/ | ||
module.exports = { | ||
plugin, | ||
isFunction, | ||
requireResource, | ||
shallowEqual, | ||
//AUTO_PUBLIC_PATH, | ||
//ABSOLUTE_PUBLIC_PATH, | ||
//SINGLE_DOT_PATH_SEGMENT, | ||
//getPublicPath, | ||
//resolvePublicPath, | ||
}; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
48825
612
654
0
4
13
7
3
+ Addedansis@^1.3.2
+ Added@babel/parser@7.26.7(transitive)
+ Added@babel/types@7.26.7(transitive)
+ Added@types/node@22.12.0(transitive)
+ Addedelectron-to-chromium@1.5.88(transitive)
- Removed@babel/parser@7.26.5(transitive)
- Removed@babel/types@7.26.5(transitive)
- Removed@types/node@22.10.10(transitive)
- Removedelectron-to-chromium@1.5.87(transitive)