Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

pug-plugin

Package Overview
Dependencies
Maintainers
1
Versions
92
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

pug-plugin - npm Package Compare versions

Comparing version 2.0.1 to 2.1.0-alpha.0

13

CHANGELOG.md
# Change log
## 2.1.0-beta.0 (2022-04-14)
DON'T use this version!\
This is only for private temporary test. This version will be soon deleted.
- feat: added supports the `responsive-loader` (EXPERIMENTAL)
## 2.0.2 (2022-04-08)
- feat: caching of an already processed asset when the same asset is required in different issuer files
- fix: conflict of multiple styles with the same filename
- fix: resolving url() in styles required in pug
- fix: potential collision when resolving resources for `compile` method
- test: caching for styles required with same name
## 2.0.1 (2022-04-03)

@@ -4,0 +17,0 @@ - fixed incorrect output directory for a module if the option `outputPath` was relative

19

package.json
{
"name": "pug-plugin",
"version": "2.0.1",
"description": "The pug plugin to handle the pug, html, css, scss files in webpack entry and extract css from pug.",
"version": "2.1.0-alpha.0",
"description": "Pug plugin to extract html, css and js from pug templates defined in webpack entry.",
"keywords": [
"pug",
"plugin",
"webpack",
"entry",
"pug",
"html",

@@ -68,3 +69,3 @@ "css",

"devDependencies": {
"@babel/core": "^7.17.8",
"@babel/core": "^7.17.9",
"@babel/preset-env": "^7.16.11",

@@ -74,11 +75,11 @@ "@types/jest": "^27.4.1",

"css-loader": "^6.7.1",
"cssnano": "^5.1.5",
"cssnano": "^5.1.7",
"html-loader": "^3.1.0",
"jest": "^27.5.1",
"material-icons": "^1.10.7",
"material-icons": "^1.10.8",
"mini-css-extract-plugin": "^2.6.0",
"postcss-loader": "^6.2.1",
"prettier": "^2.6.1",
"prettier": "^2.6.2",
"rimraf": "^3.0.2",
"sass": "^1.49.9",
"sass": "^1.50.0",
"sass-loader": "^12.6.0",

@@ -88,4 +89,4 @@ "style-loader": "^3.3.1",

"resolve-url-loader": "^5.0.0",
"webpack": "^5.71.0"
"webpack": "^5.72.0"
}
}

@@ -24,24 +24,45 @@ <div align="center">

This plugin extract HTML and CSS from `pug` `html` `scss` `css` files defined in `webpack entry` and save into separate files.
The plugin can extract CSS and JavaScript from source files required in pug, without necessary to define them in the webpack entry.
The pug plugin extract HTML, JavaScript and CSS from pug template defined in `webpack entry`.
Using the `pug-plugin` no longer requires additional plugins and loaders such as:
- [html-webpack-plugin](https://github.com/jantimon/html-webpack-plugin)
- [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin)
- [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`)
- [resolve-url-loader](https://github.com/bholloway/resolve-url-loader)
- [pug-loader](https://github.com/webdiscus/pug-loader) (this loader is already included in the `pug-plugin`)
Now is possible to use the source files of styles and scripts directly in pug.
```pug
link(href=require('./styles.scss') rel='stylesheet')
script(src=require('./main.js'))
```
The generated HTML contains hashed css and js filenames, depending on how webpack is configured.
```html
<link rel="stylesheet" href="/assets/css/styles.05e4dd86.css">
<script src="/assets/js/main.f4b855d8.js"></script>
```
> The plugin can be used not only for `pug` but also for simply extracting `HTML` or `CSS` from `webpack entry`, independent of pug usage.
Now is possible to define only pug templates in webpack entry. All styles and scripts will be automatically extracted from pug.
```js
const PugPlugin = require('pug-plugin');
module.exports = {
entry: {
'index': './src/index.pug', // extract html, css and js from pug
},
plugins: [
new PugPlugin(),
],
// ...
};
```
## Requirements
- **Webpack 5** \
⚠️ Working with Webpack 4 is not guaranteed.
- **Asset Modules** for Webpack 5: `asset/resource` `asset/inline` `asset/source` `asset` \
⚠️ Does not support deprecated modules such as `file-loader` `url-loader` `raw-loader`.
- **Pug 3** \
⚠️ By usage Pug v2.x is required extra install the `pug-walk` package. Working with Pug < v3.0.2 is not guaranteed.
> 💡 The required styles and scripts in pug do not need to define in the webpack entry.
> All required resources will be automatically handled by webpack.
The single pug plugin perform the most commonly used functions of the following packages:
| Packages | Features |
|-------------------------------------------------------------------------------------------|-----------------------------------------------------------------|
| [html-webpack-plugin](https://github.com/jantimon/html-webpack-plugin) | extract HTML from pug |
| [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) | extract CSS from styles |
| [webpack-remove-empty-scripts](https://github.com/webdiscus/webpack-remove-empty-scripts) | prevent generating empty files by the `mini-css-extract-plugin` |
| [resolve-url-loader](https://github.com/bholloway/resolve-url-loader) | resolve the url in CSS |
| [pug-loader](https://github.com/webdiscus/pug-loader) | the pug loader is already included in the pug plugin |
You can replace all of the above packages with just one pug plugin.
<a id="install" name="install" href="#install"></a>

@@ -56,3 +77,3 @@ ## Install

The minimal configuration in `webpack.config.js`:
The minimal webpack config to extract HTML from pug:
```js

@@ -59,0 +80,0 @@ const path = require('path');

@@ -70,4 +70,6 @@ const fs = require('fs');

*/
const resolveException = (file) => {
let message = `\n${ansis.black.bgRedBright(`[${plugin}]`)} Can't resolve the file ${ansis.cyan(file)}`;
const resolveException = (file, issuer) => {
let message = `\n${ansis.black.bgRedBright(`[${plugin}]`)} Can't resolve the file ${ansis.cyan(
file
)} in ${ansis.blueBright(issuer)}`;

@@ -74,0 +76,0 @@ if (path.isAbsolute(file) && !fs.existsSync(file)) {

const vm = require('vm');
const path = require('path');
const url = require('url');
const ansis = require('ansis');

@@ -11,3 +12,3 @@ const { merge } = require('webpack-merge');

const { extractHtml, extractCss } = require('./modules');
const { isFunction, pathToPosix, outToConsole } = require('./utils');
const { isFunction, pathToPosix, parseRequest, outToConsole } = require('./utils');
const { urlDependencyResolver, resourceResolver } = require('./resolver');

@@ -49,3 +50,3 @@

/**
* @typedef {Object} AssetEntry
* @typedef {Object} AssetEntryOptions
* @property {string} name The key of webpack entry.

@@ -99,55 +100,402 @@ * @property {string} file The output asset file with absolute path.

class PugPlugin {
/** @type {AssetEntry[]} */
entries = [];
entryIndex = 1;
/**
* AssetTrash singleton.
* Accumulate and remove junk assets from compilation.
*/
const AssetTrash = {
trash: [],
/**
* Add a junk js file to trash.
*
* @param {string} file
*/
toTrash(file) {
this.trash.push(file);
},
/**
* @type {{
* files: {},
* trashAssets: [],
* add(name: string, request: string, issuer: string): void,
* setIssuerFilename(issuer: string, filename: string): void,
* has(request: string): boolean,
* reset(): void
* }}
* Remove all js trash files from compilation.
*
* @param {Compilation} compilation The instance of the webpack compilation.
*/
scripts = {
files: {},
trashAssets: [],
clearCompilation(compilation) {
this.trash.forEach((file) => {
compilation.deleteAsset(file);
});
this.reset();
},
add(name, request, issuer) {
this.files[request] = {
name,
issuer: {
filename: undefined,
request: issuer,
},
};
},
/**
* Clear caches before start of this plugin.
*/
reset() {
this.trash = [];
},
};
setIssuerFilename(issuer, filename) {
for (let request in this.files) {
if (this.files[request].issuer.request === issuer) {
this.files[request].issuer.filename = filename;
/**
* AssetEntry singleton.
*/
const AssetEntry = {
/** @type {AssetEntryOptions[]} */
entries: [],
/** @type {string[]} */
addedToCompilationEntryNames: [],
compilation: null,
EntryPlugin: null,
AssetScript: null,
/**
* @param {Compilation} compilation The instance of the webpack compilation.
* @param {AssetScript} AssetScript The instance of the AssetScript.
*/
init(compilation, AssetScript) {
this.compilation = compilation;
this.EntryPlugin = compilation.compiler.webpack.EntryPlugin;
this.AssetScript = AssetScript;
this.resetAdditionalEntries();
},
/**
* @param {{}} entry The webpack entry object.
* @param {AssetEntryOptions} assetEntryOptions
* @param {string} webpackOutputPath
*/
add(entry, assetEntryOptions, webpackOutputPath) {
const { outputPath, filenameTemplate } = assetEntryOptions;
const relativeOutputPath = path.isAbsolute(outputPath) ? path.relative(webpackOutputPath, outputPath) : outputPath;
entry.filename = (pathData, assetInfo) => {
if (!assetEntryOptions.filename) {
Object.defineProperty(assetEntryOptions, 'filename', {
set(filename) {
// replace the setter with value of resolved filename
delete this.filename;
this.filename = filename;
this.file = path.join(outputPath, filename);
},
});
}
let filename = isFunction(filenameTemplate) ? filenameTemplate(pathData, assetInfo) : filenameTemplate;
if (relativeOutputPath) {
filename = path.posix.join(relativeOutputPath, filename);
}
return filename;
};
this.entries.push(assetEntryOptions);
},
/**
* Add a resource from pug, e.g. script, to webpack compilation.
*
* @param {string} importFile
* @param {string} filenameTemplate
* @param {string} outputPath
* @param {string} context
* @param {string} issuer
* @return {boolean}
*/
addToCompilation({ importFile, filenameTemplate, outputPath, context, issuer }) {
let name = this.AssetScript.getUniqueName(importFile, issuer);
// ignore duplicate entries with same name
if (name === false) return false;
const entry = {
name,
runtime: undefined,
layer: undefined,
dependOn: undefined,
baseUri: undefined,
publicPath: undefined,
chunkLoading: undefined,
asyncChunks: undefined,
wasmLoading: undefined,
library: undefined,
};
/** @type {AssetEntryOptions} */
const assetEntryOptions = {
name,
filenameTemplate,
filename: undefined,
file: undefined,
importFile,
sourcePath: context,
outputPath,
postprocess: undefined,
extract: undefined,
verbose: false,
};
this.add(entry, assetEntryOptions, outputPath);
// adds the entry of the script from pug to the compilation
// see reference: node_modules/webpack/lib/EntryPlugin.js
const entryDependency = this.EntryPlugin.createDependency(importFile, { name });
this.compilation.addEntry(context, entryDependency, entry, (err) => {
if (err) addEntryException(err, name);
});
this.addedToCompilationEntryNames.push(name);
return true;
},
/**
* @param {string} name The entry name.
* @returns {AssetEntryOptions}
*/
findByName(name) {
return this.entries.find((entry) => entry.name === name);
},
/**
* @param {Module} module The chunk module.
* @returns {boolean}
*/
isEntryModule(module) {
if (!module.resource) return false;
const { resource } = parseRequest(module.resource);
return this.entries.find((entry) => entry.importFile === resource) !== undefined;
},
/**
* Reset entries added not via webpack entry.
* This is important for webpack watch and serve.
*/
resetAdditionalEntries() {
for (const entryName of this.addedToCompilationEntryNames) {
const index = this.entries.findIndex((entry) => entry.name === entryName);
this.entries.splice(index, 1);
}
this.addedToCompilationEntryNames = [];
},
clear() {
this.entries = [];
this.addedToCompilationEntryNames = [];
},
};
/**
* AssetScript singleton.
*/
const AssetScript = {
files: [],
index: 1,
/**
* Replace all required source filenames with generating asset filenames.
* Note: this method must be called in the afterProcessAssets compilation hook.
*
* @param {Compilation} compilation The instance of the webpack compilation.
* @param {string} outputPublicPath The output public path.
*/
replaceSourceFilesInCompilation(compilation, outputPublicPath) {
const RawSource = compilation.compiler.webpack.sources.RawSource;
let existsScripts = [];
// in the content, replace the source script file with the output filename
for (let asset of this.files) {
let { request: sourceFile, filename: assetFile, chunkFiles } = asset;
const issuerFile = asset.issuer.filename;
if (!compilation.assets.hasOwnProperty(issuerFile)) {
// let's show an original error
continue;
}
const content = compilation.assets[issuerFile].source();
let chunkScripts = '',
chunkFile,
newContent;
// save asset filenames to cache for webpack serve, watch
if (assetFile == null) {
const chunkGroup = compilation.namedChunkGroups.get(asset.name);
const entrypointChunk = chunkGroup.getEntrypointChunk();
chunkFile = entrypointChunk.files.values().next().value;
assetFile = path.posix.join(outputPublicPath, chunkFile);
chunkFiles = chunkGroup.getFiles();
asset.filename = assetFile;
asset.chunkFiles = chunkFiles;
}
// replace source filename with asset filename
if (chunkFiles.length > 1) {
existsScripts.push(chunkFile);
// generate additional scripts of chunks
for (let file of chunkFiles) {
if (existsScripts.indexOf(file) < 0) {
const scriptFile = path.posix.join(outputPublicPath, file);
chunkScripts += `<script src="${scriptFile}"></script>`;
existsScripts.push(file);
}
}
// inject generated chunks before original <script> and replace source file with output filename
const srcStartPos = content.indexOf(sourceFile);
const srcEndPos = srcStartPos + sourceFile.length;
let tagStartPos = srcStartPos;
while (tagStartPos >= 0 && content.charAt(--tagStartPos) !== '<') {}
newContent =
content.slice(0, tagStartPos) +
chunkScripts +
content.slice(tagStartPos, srcStartPos) +
assetFile +
content.slice(srcEndPos);
} else {
newContent = content.replace(sourceFile, assetFile);
}
},
has(request) {
return this.files.hasOwnProperty(request);
},
compilation.assets[issuerFile] = new RawSource(newContent);
}
getResource(request) {
const [resource, query] = request.split('?');
// --> console.log('\n -+-+-+ replaceSourceFilesInCompilation:\n', this.files, existsScripts);
},
return query === 'isScript' ? resource : null;
},
/**
* @param {string} request The source file of asset.
* @param {string} issuer The issuer of the asset.
* @return {string | false} return false if the file was already processed else return unique assetFile
*/
getUniqueName(request, issuer) {
let { name } = path.parse(request);
reset() {
this.files = {};
this.trashAssets = [];
},
};
const entry = AssetEntry.findByName(name);
let uniqueName = name;
let result = name;
// the entrypoint name must be unique, if already exists then add index: `main` => `main.1`, etc
if (entry) {
if (entry.importFile === request) {
result = false;
} else {
uniqueName = name + '.' + this.index++;
result = uniqueName;
}
}
this.add(uniqueName, request, issuer);
return result;
},
/**
* @param {string} name The unique name of entry point.
* @param {string} request The required resource file.
* @param {string} issuer The source file of issuer of the required file.
*/
add(name, request, issuer) {
// prevent to add duplicates by webpack serve, watch
if (this.files.find((item) => item.request === request && item.issuer.request === issuer)) return;
this.files.push({
name,
request,
filename: undefined,
chunkFiles: [],
issuer: {
filename: undefined,
request: issuer,
},
});
},
/**
*
* @param {string} issuer The source file of issuer of the required file.
* @param {string} filename The asset filename of issuer.
*/
setIssuerFilename(issuer, filename) {
for (let item of this.files) {
if (item.issuer.request === issuer) {
item.issuer.filename = filename;
}
}
},
has(request) {
return this.files.find((item) => item.request === request);
},
getResource(request) {
const { resource, query } = parseRequest(request);
return query === 'isScript' ? resource : null;
},
/**
* Reset cache before new compilation by webpack watch or serve.
*/
reset() {
// don't reset files because this cache is used by webpack watch or serve
//this.files = [];
this.index = 1;
},
/**
* Clear caches before start of this plugin.
*/
clear() {
this.files = [];
this.index = 1;
},
};
/**
* AssetModule singleton.
*/
const AssetModule = {
files: [],
index: {},
/**
* @param {string} sourceFile
* @param {string} assetFile
* @return {string|boolean} return false if the file was already processed else return unique assetFile
*/
getUniqueFilename(sourceFile, assetFile) {
const sameAssets = this.files.filter((item) => item.filename === assetFile) || [];
let uniqueFilename = assetFile;
if (sameAssets.length > 0) {
if (sameAssets.find((item) => item.request === sourceFile)) return false;
let index = '.' + this.index[assetFile]++;
let pos = assetFile.lastIndexOf('.');
// paranoid filename extension check, should normally never occur
if (pos < 0) pos = assetFile.length;
uniqueFilename = assetFile.slice(0, pos) + index + assetFile.slice(pos);
} else {
// start index of same asset filename, eg: styles.1.css
this.index[assetFile] = 1;
}
this.files.push({
request: sourceFile,
filename: assetFile,
});
return uniqueFilename;
},
reset() {
this.files = [];
},
};
/**
* Class PugPlugin.
*/
class PugPlugin {
entryLibrary = {

@@ -176,18 +524,23 @@ name: 'return',

const { RawSource } = compiler.webpack.sources;
const { HotUpdateChunk } = compiler.webpack;
const webpackOptions = compiler.options;
const {
path: webpackOutputPath,
publicPath: webpackOutputPublicPath,
filename: webpackOutputFilename,
publicPath: webpackPublicPath,
filename: webpackScriptFilename,
} = webpackOptions.output;
const { RawSource } = compiler.webpack.sources;
const { EntryPlugin, HotUpdateChunk } = compiler.webpack;
// TODO: resolveInPaths 'auto' publicPath
if (webpackOutputPublicPath == null || webpackOutputPublicPath === 'auto') publicPathException();
if (webpackPublicPath == null || webpackPublicPath === 'auto') publicPathException();
resourceResolver.init({
publicPath: webpackOutputPublicPath,
publicPath: webpackPublicPath,
});
// clear caches by tests, webpack watch or serve
AssetEntry.clear();
AssetScript.clear();
AssetTrash.reset();
// enable library type `jsonp` for compilation JS from source into HTML string via Function()

@@ -215,3 +568,3 @@ if (webpackOptions.output.enabledLibraryTypes.indexOf('jsonp') < 0) {

const importFile = entry.import[0];
let sourceFile = this.getFileFromImport(importFile);
let { resource: sourceFile } = parseRequest(importFile);
const module = this.getModule(sourceFile);

@@ -238,11 +591,11 @@

/** @type {AssetEntry} */
const assetEntry = {
name: name,
filenameTemplate: filenameTemplate,
/** @type {AssetEntryOptions} */
const assetEntryOptions = {
name,
filenameTemplate,
filename: undefined,
file: undefined,
importFile: sourceFile,
sourcePath: sourcePath,
outputPath: outputPath,
sourcePath,
outputPath,
library: entry.library,

@@ -254,118 +607,59 @@ postprocess: isFunction(postprocess) ? postprocess : null,

const relativeOutputPath = path.isAbsolute(outputPath)
? path.relative(webpackOutputPath, outputPath)
: outputPath;
entry.filename = (pathData, assetInfo) => {
if (!assetEntry.filename) {
Object.defineProperty(assetEntry, 'filename', {
set(filename) {
// replace the setter with value of resolved filename
delete this.filename;
this.filename = filename;
this.file = path.join(webpackOutputPath, filename);
},
});
}
let filename = isFunction(filenameTemplate) ? filenameTemplate(pathData, assetInfo) : filenameTemplate;
filename = path.posix.join(relativeOutputPath, filename);
return filename;
};
this.entries.push(assetEntry);
AssetEntry.add(entry, assetEntryOptions, webpackOutputPath);
}
});
// Normal Module Factory
compiler.hooks.normalModuleFactory.tap(plugin, (normalModuleFactory) => {
urlDependencyResolver.init(normalModuleFactory.fs.fileSystem);
this.scripts.reset();
// This compilation
compiler.hooks.thisCompilation.tap(plugin, (compilation, { normalModuleFactory, contextModuleFactory }) => {
const verbose = this.verbose;
this.compilation = compilation;
normalModuleFactory.hooks.createModule.tap(plugin, (createData, resolveData) => {
const { context, rawRequest, resource } = createData;
if (rawRequest !== resource) {
resourceResolver.addResolvedFile(context, rawRequest, resource);
}
});
urlDependencyResolver.init(normalModuleFactory.fs.fileSystem, compiler.options);
AssetEntry.init(compilation, AssetScript);
AssetTrash.reset();
AssetScript.reset();
AssetModule.reset();
// before resolve
normalModuleFactory.hooks.beforeResolve.tap(plugin, (resolveData) => {
const compilation = this.compilation;
const { context, request } = resolveData;
const scriptResource = this.scripts.getResource(request);
const importFile = AssetScript.getResource(request);
if (scriptResource != null) {
if (importFile) {
const { issuer } = resolveData.contextInfo;
let { name } = path.parse(scriptResource);
const existsName = this.entries.find((item) => item.name === name);
const res = AssetEntry.addToCompilation({
importFile,
filenameTemplate: webpackScriptFilename,
outputPath: webpackOutputPath,
context,
issuer,
});
if (!res) return false;
// the entrypoint name must be unique, if already exists then add index: `main` => `main1`, `main2`, etc
if (existsName) name += this.entryIndex++;
this.scripts.add(name, scriptResource, issuer);
resolveData.request = scriptResource;
resolveData.request = importFile;
} else if (resolveData.dependencyType === 'url') {
urlDependencyResolver.resolve(resolveData);
}
});
// TODO: refactoring - add to entry as function
const outputPath = webpackOutputPath;
const relativeOutputPath = path.relative(webpackOutputPath, outputPath);
const filenameTemplate = webpackOutputFilename;
const assetEntry = {
name,
filenameTemplate: filenameTemplate,
filename: undefined,
file: undefined,
importFile: scriptResource,
sourcePath: context,
outputPath: outputPath,
postprocess: undefined,
extract: undefined,
verbose: true,
};
// after create module
normalModuleFactory.hooks.module.tap(plugin, (module, createData, resolveData) => {
const { context, rawRequest, resource } = createData;
const entryOptions = {
name,
filename(pathData, assetInfo) {
if (!assetEntry.filename) {
Object.defineProperty(assetEntry, 'filename', {
set(filename) {
// replace the setter with value of resolved filename
delete this.filename;
this.filename = filename;
this.file = path.join(webpackOutputPath, filename);
},
});
}
if (rawRequest !== resource) {
resourceResolver.addResolvedFile(context, rawRequest, resource);
}
let filename = isFunction(filenameTemplate) ? filenameTemplate(pathData, assetInfo) : filenameTemplate;
filename = path.posix.join(relativeOutputPath, filename);
return filename;
},
runtime: undefined,
layer: undefined,
dependOn: undefined,
baseUri: undefined,
publicPath: undefined,
chunkLoading: undefined,
asyncChunks: undefined,
wasmLoading: undefined,
library: undefined,
};
if (resolveData.dependencyType === 'url') {
const issuer = resolveData.contextInfo.issuer;
// see reference: node_modules/webpack/lib/EntryPlugin.js
const dep = EntryPlugin.createDependency(scriptResource, { name });
compilation.addEntry(context, dep, entryOptions, (err) => {
if (err) addEntryException(err, name);
});
this.entries.push(assetEntry);
} else if (resolveData.dependencyType === 'url') {
urlDependencyResolver.resolve(resolveData);
// TODO: detect correct type by usage other loaders like 'responsive-loader'
module.isDependencyTypeUrl = true;
if (resolveData?.contextInfo.issuer) {
resourceResolver.addToModuleCache(resource, rawRequest, issuer);
}
}
});
});
// This compilation
compiler.hooks.thisCompilation.tap(plugin, (compilation) => {
const verbose = this.verbose;
this.compilation = compilation;
// build module

@@ -389,3 +683,3 @@ compilation.hooks.buildModule.tap(plugin, (module) => {

const entry = this.getEntryByName(chunk.name);
const entry = AssetEntry.findByName(chunk.name);

@@ -395,2 +689,3 @@ // process only entries supported by this plugin

const chunkModules = chunkGraph.getChunkModulesIterable(chunk);
const sources = new Set();

@@ -400,17 +695,22 @@ const contentHashType = 'javascript';

// the current resource issuer
let resourceIssuer = '';
entry.filename = assetFile;
this.scripts.setIssuerFilename(entry.importFile, assetFile);
resourceResolver.scripts = this.scripts;
resourceResolver.clearChunkCache();
AssetScript.setIssuerFilename(entry.importFile, assetFile);
resourceResolver.scripts = AssetScript;
if (verbose) this.verboseEntry(entry);
for (const module of chunkGraph.getChunkModules(chunk)) {
for (const module of chunkModules) {
if (!module.resource) continue;
const sourceFile = module.resource;
const context = path.dirname(sourceFile);
// add needless chunks to trash
if (this.scripts.has(module.resource)) {
if (AssetScript.has(sourceFile)) {
const file = module.buildInfo.filename;
if (file != null) {
this.scripts.trashAssets.push(file);
AssetTrash.toTrash(file);
}

@@ -422,5 +722,5 @@ continue;

const resourceResolveDataContext = module.resourceResolveData.context || {};
const issuerFile = resourceResolveDataContext.issuer || module.resource;
const issuerFile = resourceResolveDataContext.issuer || sourceFile;
if (this.isEntryModule(module) && chunkGraph.isEntryModuleInChunk(module, chunk)) {
if (AssetEntry.isEntryModule(module) && chunkGraph.isEntryModuleInChunk(module, chunk)) {
// entry-point

@@ -431,3 +731,3 @@ const source = module.originalSource();

const pluginModule = this.getModule(module.resource) || entry;
const pluginModule = this.getModule(sourceFile) || entry;

@@ -457,2 +757,4 @@ const postprocessInfo = {

});
resourceIssuer = entry.importFile;
} else if (module.type === 'javascript/auto') {

@@ -465,11 +767,10 @@ // require a resource supported via the plugin module, e.g. style

const pluginModule = this.getModule(module.resource);
const pluginModule = this.getModule(sourceFile);
if (!pluginModule) continue;
let { name } = path.parse(sourceFile);
const filenameTemplate = pluginModule.filename ? pluginModule.filename : entry.filenameTemplate;
// TODO: generate content hash for assets required in pug for true [contenthash] in filename
// origin: buildInfo.assetInfo.contenthash or buildInfo.fullContentHash
const hash = buildInfo.assetInfo ? buildInfo.assetInfo.contenthash : buildInfo.hash;
const { name } = path.parse(module.resource);
// TODO: research why sometimes the id is relative path instead of a number, it's ok?

@@ -488,3 +789,9 @@ const id = chunkGraph.getModuleId(module);

};
const assetFile = compilation.getAssetPath(filenameTemplate, contextData);
let assetFile = compilation.getAssetPath(filenameTemplate, contextData);
assetFile = AssetModule.getUniqueFilename(sourceFile, assetFile);
// skip already processed assets
if (assetFile === false) continue;
const postprocessInfo = {

@@ -494,3 +801,3 @@ isEntry: false,

filename: filenameTemplate,
sourceFile: module.resource,
sourceFile,
outputFile: entry.file,

@@ -500,8 +807,6 @@ assetFile,

resourceResolver.addToChunkCache(module, assetFile);
if (verbose) {
this.verboseExtractModule({
issuerFile,
sourceFile: module.resource,
sourceFile,
assetFile: path.join(webpackOutputPath, assetFile),

@@ -515,3 +820,3 @@ });

source: source.source().toString(),
sourceFile: module.resource,
sourceFile,
assetFile,

@@ -526,13 +831,126 @@ pluginModule,

});
//resourceResolver.addToChunkCache(module, assetFile);
resourceResolver.addToChunkCache(module, path.posix.join(webpackPublicPath, assetFile));
//resourceResolver.addToChunkCache2(context, module.rawRequest, assetFile);
resourceIssuer = sourceFile;
} else if (module.type === 'asset/resource') {
// require a resource
const assetFile = buildInfo.filename;
resourceResolver.addToChunkCache(module, assetFile);
// require a resource in pug or in css via url()
let assetFile = buildInfo.filename;
if (verbose)
// TODO: refactoring code
// -- BEGIN resource loader --
const resourceQuery = url.parse(module.rawRequest, true).query;
// console.log(
// '\n +++ addToChunkCache2: ',
// assetFile,
// module.type,
// module.isDependencyTypeUrl,
// '\n',
// context,
// '\n- rawRequest: ',
// module.rawRequest,
// '\n- assetFile: ',
// assetFile,
// //'\n- assets: ',
// //buildInfo.assets,
// //'\n- assetsInfo: ',
// // buildInfo.assetsInfo,
// '\n- resourceQuery: ',
// resourceQuery
// );
if (resourceQuery && resourceQuery.prop) {
const prop = resourceQuery.prop;
const source = module?.originalSource()?.source()?.toString();
//console.log(' ~~~~ COMPILED source: ', source);
if (source) {
const contextObject = vm.createContext({
__webpack_public_path__: webpackPublicPath,
module: { exports: {} },
});
const script = new vm.Script(source, { filename: sourceFile });
const result = script.runInContext(contextObject);
if (result && result.hasOwnProperty(prop)) {
AssetTrash.toTrash(assetFile);
assetFile = result[prop];
//console.log('\n~~~~ COMPILED ASSET: ', resourceQuery);
resourceResolver.addToChunkCache(module, assetFile);
if (verbose) {
this.verboseExtractResource({
issuerFile,
sourceFile,
outputPath: webpackOutputPath,
assetFile: assetFile,
});
}
continue;
}
}
}
// get the real filename of the asset by usage a loader for the resource, e.g. `responsive-loader`
// and add the original asset file to trash to remove it from compilation
if (buildInfo.assetsInfo != null) {
const assets = Array.from(buildInfo.assetsInfo.keys());
if (assets.length > 0) {
// dummy size for srcSec attribute with more than 2 files
let defaultSize = assets.length > 1 ? ' 1' : '';
const realAssetFile = assets
.map((value) => path.posix.join(webpackPublicPath, value) + defaultSize)
.join(',');
if (realAssetFile) {
AssetTrash.toTrash(assetFile);
assetFile = realAssetFile;
// TODO: add original assetFile to trash
//console.log('\n xxx DELETE original assetFile: ', buildInfo.filename, ' => OK: ', assetFile);
resourceResolver.addToChunkCache(module, assetFile);
if (verbose) {
this.verboseExtractResource({
issuerFile,
sourceFile,
outputPath: webpackOutputPath,
assetFile: assetFile,
});
}
continue;
// assetFile = {
// src: 'img/favicon.2ff47510-300w.webp',
// srcSet: 'img/favicon.2ff47510-300w.webp 300w',
// };
}
}
}
// if (assetFile !== buildInfo.filename) {
// console.log('\n === REAL assetFile: ', buildInfo.filename, ' => ', assetFile);
// }
// -- END resource loader --
assetFile = path.posix.join(webpackPublicPath, assetFile);
if (verbose) {
this.verboseExtractResource({
issuerFile,
sourceFile: module.resource,
assetFile: path.join(webpackOutputPath, assetFile),
sourceFile,
outputPath: webpackOutputPath,
assetFile: assetFile,
});
}
if (module.isDependencyTypeUrl) {
resourceResolver.setAssetFileInModuleCache(module.resource, assetFile);
} else {
//console.log('\n +++ addToChunkCache:\n', context, '\n', '\n- ', module.rawRequest, '\n- ', assetFile);
resourceResolver.addToChunkCache(module, assetFile);
//resourceResolver.addToChunkCache2(context, module.rawRequest, assetFile);
}
}

@@ -564,7 +982,6 @@ }

// called direct after renderManifest
// direct after renderManifest
compilation.hooks.chunkAsset.tap(plugin, (chunk, file) => {
// avoid saving runtime js files from node_modules as assets by usage of the splitChunks optimization,
// because it is never used in assets, it's wrong extracted files by webpack
if (chunk.chunkReason && chunk.chunkReason.indexOf('defaultVendors') > 0) {

@@ -580,3 +997,3 @@ const modules = compilation.chunkGraph.getChunkModules(chunk);

) {
this.scripts.trashAssets.push(file);
AssetTrash.toTrash(file);
}

@@ -591,6 +1008,3 @@ }

(assets) => {
// clean trash
this.scripts.trashAssets.forEach((file) => {
compilation.deleteAsset(file);
});
AssetTrash.clearCompilation(compilation);
}

@@ -602,54 +1016,3 @@ );

compilation.hooks.afterProcessAssets.tap(plugin, () => {
const RawSource = compiler.webpack.sources.RawSource;
let existsScripts = [];
for (let sourceFile in this.scripts.files) {
const item = this.scripts.files[sourceFile];
const issuerFile = item.issuer.filename;
const chunkGroup = compilation.namedChunkGroups.get(item.name);
const entrypointChunk = chunkGroup.getEntrypointChunk();
const chunkFile = entrypointChunk.files.values().next().value;
const outputFile = path.posix.join(webpackOutputPublicPath, chunkFile);
// replace source filename with asset filename
const chunkFiles = chunkGroup.getFiles();
const asset = compilation.assets[issuerFile];
// continue by a potential error, normally never occur
if (asset == null) continue;
const content = asset.source();
let chunkScripts = '';
let newContent;
if (chunkFiles.length > 1) {
existsScripts.push(chunkFile);
// generate additional scripts of chunks
for (let file of chunkFiles) {
if (existsScripts.indexOf(file) < 0) {
const scriptFile = path.posix.join(webpackOutputPublicPath, file);
chunkScripts += `<script src="${scriptFile}"></script>`;
existsScripts.push(file);
}
}
// inject generated chunks before original <script> and replace source file with output filename
const srcStartPos = content.indexOf(sourceFile);
const srcEndPos = srcStartPos + sourceFile.length;
let tagStartPos = srcStartPos;
while (tagStartPos >= 0 && content.charAt(--tagStartPos) !== '<') {}
newContent =
content.slice(0, tagStartPos) +
chunkScripts +
content.slice(tagStartPos, srcStartPos) +
outputFile +
content.slice(srcEndPos);
} else {
newContent = content.replace(sourceFile, outputFile);
}
compilation.assets[issuerFile] = new RawSource(newContent);
}
AssetScript.replaceSourceFilesInCompilation(compilation, webpackPublicPath);
});

@@ -671,18 +1034,17 @@ });

let result, compiledCode;
resourceResolver.setCurrentContext(path.dirname(sourceFile));
resourceResolver.setIssuer(sourceFile);
if (pluginModule && pluginModule.compile) {
compiledCode = pluginModule.compile(source, assetFile);
} else {
const contextOptions = {
require: resourceResolver.require,
// the `module.id` is required for `css-loader`, in module extractCss expected as source path
module: { id: sourceFile },
};
const contextObject = vm.createContext(contextOptions);
const sourceCjs = this.toCommonJS(source);
const script = new vm.Script(sourceCjs, { filename: sourceFile });
compiledCode = script.runInContext(contextObject) || '';
}
const sourceCjs = this.toCommonJS(source);
const contextOptions = {
require: resourceResolver.require,
// the `module.id` is required for `css-loader`, in module extractCss expected as source path
module: { id: sourceFile },
};
const contextObject = vm.createContext(contextOptions);
const script = new vm.Script(sourceCjs, { filename: sourceFile });
compiledCode = script.runInContext(contextObject) || '';
//if (assetFile === 'index.html') console.log('### compiledCode: ', assetFile, sourceCjs, '\n\n:', compiledCode);
try {

@@ -741,45 +1103,13 @@ result = isFunction(compiledCode) ? compiledCode() : compiledCode;

/**
* Return the file part from import file.
* For example: from `index.pug?data={"key":"value"}` return `index.pug`
*
* @param {string} str
* @returns {string}
*/
getFileFromImport(str) {
return str.split('?', 1)[0];
}
/**
* @param {string} name The entry name.
* @returns {AssetEntry}
*/
getEntryByName(name) {
return this.entries.find((entry) => entry.name === name);
}
/**
* @param {string} filename The filename of a source file, can contain a query string.
* @param {string} request The request of a source file, can contain a query string.
* @returns {ModuleOptions}
*/
getModule(filename) {
return this.options.modules.find(
(module) => module.enabled !== false && module.test.test(this.getFileFromImport(filename))
);
getModule(request) {
const { resource } = parseRequest(request);
return this.options.modules.find((module) => module.enabled !== false && module.test.test(resource));
}
/**
* @param {Module} module The chunk module.
* @returns {boolean}
* @param {AssetEntryOptions} entry
*/
isEntryModule(module) {
const importFile = module.resource;
return importFile
? this.entries.find((entry) => entry.importFile === this.getFileFromImport(importFile)) !== undefined
: false;
}
/**
* @param {AssetEntry} entry
*/
verboseEntry(entry) {

@@ -789,3 +1119,3 @@ if (!entry) return;

`${ansis.black.bgYellow(`[${plugin}]`)} Compile the entry ${ansis.green(entry.name)}\n` +
` - filename: ${
` filename: ${
isFunction(entry.filenameTemplate)

@@ -795,4 +1125,4 @@ ? ansis.greenBright('[Function: filename]')

}\n` +
` - source: ${ansis.cyan(entry.importFile)}\n` +
` - output: ${ansis.cyanBright(entry.file)}\n`
` source: ${ansis.cyan(entry.importFile)}\n` +
` output: ${ansis.cyanBright(entry.file)}\n`
);

@@ -810,4 +1140,4 @@ }

`${ansis.green(issuerFile)}\n` +
` - source: ${ansis.cyan(sourceFile)}\n` +
` - output: ${ansis.cyanBright(assetFile)}\n`
` source: ${ansis.cyan(sourceFile)}\n` +
` output: ${ansis.cyanBright(assetFile)}\n`
);

@@ -819,10 +1149,13 @@ }

* @param {string} sourceFile
* @param {string} outputPath
* @param {string} assetFile
*/
verboseExtractResource({ issuerFile, sourceFile, assetFile }) {
verboseExtractResource({ issuerFile, sourceFile, outputPath, assetFile }) {
outToConsole(
`${ansis.black.bgYellow(`[${plugin}]`) + ansis.black.bgGreen(` Extract Resource `)} in ` +
`${ansis.green(issuerFile)}\n` +
` - source: ${ansis.cyan(sourceFile)}\n` +
` - output: ${ansis.cyanBright(assetFile)}\n`
` source: ${ansis.cyan(sourceFile)}\n` +
//` output: ${ansis.cyanBright(assetFile)}\n`
` output path: ${ansis.cyanBright(outputPath)}\n` +
` asset: ${ansis.cyanBright(assetFile)}\n`
);

@@ -829,0 +1162,0 @@ }

@@ -13,4 +13,5 @@ const path = require('path');

* @param {FileSystem} fs
* @param {Object} options The webpack options.
*/
init(fs) {
init(fs, options) {
this.fs = fs;

@@ -85,2 +86,3 @@ },

const dependency = resolveData.dependencies[0];
// TODO: use ModuleGraph.getParentModule(dependency);

@@ -90,3 +92,3 @@ const parentModule = dependency._parentModule || {};

const snapshot = buildInfo.snapshot || {};
const issuers = snapshot.fileTimestamps;
const issuers = snapshot.fileTimestamps || snapshot.fileTshs;
/** @type {string} closest issuer that can import the resource */

@@ -111,11 +113,2 @@ const closestIssuer = issuers != null && issuers.size > 0 ? Array.from(issuers.keys()).pop() : null;

if (resolvedFile != null) {
// TODO: delete commented code in next version
// let relativeFile = path.relative(resolveData.context, resolvedFile);
// if (relativeFile[0] !== '.') {
// relativeFile = './' + relativeFile;
// }
// resolveData.request = relativeFile;
// dependency.request = relativeFile;
// dependency.userRequest = relativeFile;
resolveData.request = resolvedFile;

@@ -132,4 +125,2 @@ dependency.request = resolvedFile;

* Resolve required resources.
*
* @type {{addResolvedPath(string): void, init({publicPath: string}): void, resolveAsset(string): (string|null), webpackOptionsResolve: {}, addToChunkCache({}, string): void, chunkCache: {paths: Set<any>, files: Map<any, any>}, getId(string, string): symbol, require(string): string, clearCache(): void, publicPath: string, addResolvedFile(string, string, string): void, clearChunkCache(): void, context: string, setCurrentContext(string): void, globalCache: {paths: Set<any>, files: Map<any, any>}}}
*/

@@ -174,2 +165,9 @@ const resourceResolver = {

/**
* @type {Map}
* key is rawRequest
* value is list of contexts
*/
moduleCache: new Map(),
/**
* @param {string} publicPath

@@ -179,2 +177,4 @@ */

this.publicPath = publicPath;
// clean cache for multiple calling of webpack.run(), e.g. by tests, webpack watch or webpack serve
this.clearChunkCache();
},

@@ -212,9 +212,13 @@

this.chunkCache.paths.clear();
this.moduleCache.clear();
},
/**
* @param {string} context
* Set the current source file, which is the issuer for assets when compiling the source code.
*
* @param {string} issuer
*/
setCurrentContext(context) {
this.context = context;
setIssuer(issuer) {
this.issuer = issuer;
this.context = path.dirname(issuer);
},

@@ -225,9 +229,9 @@

*
* @param {{}} module The chunk module.
* @param {Module} module The webpack chunk module.
* @param {string} assetFile The web path of the asset.
*/
addToChunkCache(module, assetFile) {
const request = module.rawRequest;
const resourceContext = module.resourceResolveData.context;
const context = resourceContext.issuer ? path.dirname(resourceContext.issuer) : module.context;
const request = module.rawRequest;

@@ -240,2 +244,62 @@ const assetId = this.getId(context, request);

/**
* Add the context and resolved path of the resource to resolve it in require() at render time.
*
* @param {string} context The context directory of required resource.
* @param {string} request The raw request.
* @param {string} assetFile The web path of the asset.
*/
addToChunkCache2(context, request, assetFile) {
// Note: when the same resource module is used multiple times,
// only the first module is stored in the chunk,
// so using the issuer of the resource module is not reliable.
// The true issuer is the last used parent module of this resource module.
const assetId = this.getId(context, request);
this.chunkCache.files.set(assetId, assetFile);
this.chunkCache.paths.add(context);
},
/**
* @param {string} resource The full path of source resource.
* @param {string} rawRequest The raw request of resource is argument of URL() in css.
* @param {string} issuer The parent file of the resource.
*/
addToModuleCache(resource, rawRequest, issuer) {
if (!this.moduleCache.has(resource)) {
this.moduleCache.set(resource, {
issuers: [],
rawRequest,
assetFile: undefined,
});
}
this.moduleCache.get(resource).issuers.push(issuer);
},
/**
* @param {string} resource The full path of source resource.
* @param {string} assetFile The output asset filename.
*/
setAssetFileInModuleCache(resource, assetFile) {
if (!this.moduleCache.has(resource)) return;
this.moduleCache.get(resource).assetFile = assetFile;
},
/**
* @param {string} rawRequest The raw request of resource is argument of URL() in css.
* @param {string} issuer The parent file of the resource.
* @return {null|string|*}
*/
findAssetFileInModuleCache(rawRequest, issuer) {
for (const item of this.moduleCache.values()) {
if (item.rawRequest === rawRequest && item.issuers.indexOf(issuer) >= 0) {
return item.assetFile;
}
}
return null;
},
/**
* Add resolved path to global cache.

@@ -271,19 +335,42 @@ *

resolveAsset(file) {
const { issuer, context } = this;
let dir, assetId, assetFile;
// firstly try to find an asset in resolved directories of the chunk
// this is mostly used case by resolve a required resource in pug
// console.log(
// '\n ====> RESOLVE:',
// file,
// '\n - issuer: ',
// issuer,
// '\n - context: ',
// context,
// '\n',
// this.chunkCache.files,
// this.chunkCache.paths,
// this.moduleCache,
// this.globalCache.paths
// );
// try to resolve a resource required in pug by absolute path of source file
if (path.isAbsolute(file)) {
assetId = this.getId('', file);
assetFile = this.chunkCache.files.get(assetId);
if (assetFile != null) return assetFile;
}
// try to resolve a resource required in pug relative by context directory
for (dir of this.chunkCache.paths) {
if (dir.indexOf(context) < 0) continue;
assetId = this.getId(dir, file);
assetFile = this.chunkCache.files.get(assetId);
if (assetFile != null) {
return assetFile;
}
if (assetFile != null) return assetFile;
}
// secondly try to find an asset in resolved directories of global cache
// this case can be by resolve a resource from url used in css
// try to resolve ta resource required via url in css
assetFile = this.findAssetFileInModuleCache(file, issuer);
if (assetFile != null) return assetFile;
// try to resolve a resource imported from `node_modules` via url in css
for (dir of this.globalCache.paths) {
assetId = this.getId(dir, file);
assetFile = this.chunkCache.files.get(assetId);
const fullPath = path.resolve(dir, file);
assetFile = this.findAssetFileInModuleCache(fullPath, issuer);
if (assetFile != null) {

@@ -294,3 +381,3 @@ return assetFile;

// at the end try to resolve the full path of file and then try to resolve an asset by this full path
// try to resolve the full path of the file and then try to resolve an asset by resolved file
// this case can be by usage an alias retrieved using webpack resolve.plugins

@@ -307,39 +394,54 @@ const resolvedFile = this.globalCache.files.get(file);

/**
* Require the resource file in the compiled pug or css.
* Require the resource request in the compiled pug or css.
*
* @param {string} file The required file from source directory.
* @param {string} request The request of source resource.
* @returns {string} The output asset filename generated by filename template.
* @throws {Error}
*/
require(file) {
require(request) {
const self = resourceResolver;
// bypass the inline data-url, e.g.: data:image/png;base64
if (file.startsWith('data:')) return file;
if (request.startsWith('data:')) return request;
// @import CSS rule is not supported.
if (file.indexOf('??ruleSet') > 0) resolveException(file);
if (request.indexOf('??ruleSet') > 0) resolveException(request);
const assetFile = self.resolveAsset(file);
// require resources
const assetFile = self.resolveAsset(request);
if (assetFile) {
if (assetFile === 'img/1033740.9f92ec83-320w.webp') {
console.log('\n --- RESOLVED assetFile: ', assetFile);
// return {
// src: 'img/favicon.2ff47510-300w.webp',
// srcSet: 'img/favicon.2ff47510-300w.webp 300w',
// };
}
if (assetFile) {
//console.log('\n *** REQUIRE assetFile: ', file, assetFile);
return path.posix.join(self.publicPath, assetFile);
// if (typeof assetFile !== 'string') {
// console.log('\n --- RESOLVED assetFile: ', this.issuer, assetFile);
// //return "module.exports = { src: '### SRC ###', srcSet: '### srcSet ###' };";
// return {
// src: 'img/favicon.2ff47510-300w.webp',
// srcSet: 'img/favicon.2ff47510-300w.webp 300w',
// };
// }
//return typeof assetFile === 'string' ? path.posix.join(self.publicPath, assetFile) : assetFile;
return assetFile;
}
// require script in tag <script src=require('./main.js')>
const scriptResource = self.scripts.getResource(file);
if (scriptResource != null) {
//console.log('\n *** REQUIRE script: ', file, '\n', scriptResource, self.scripts);
return scriptResource;
}
const file = self.scripts.getResource(request);
if (file != null) return file;
// require only js code or json data
if (/\.js[a-z0-9]*$/i.test(file)) {
const fullPath = path.resolve(self.context, file);
//console.log('\n *** REQUIRE JS: ', file, fullPath);
if (/\.js[a-z0-9]*$/i.test(request)) {
const fullPath = path.resolve(self.context, request);
return require(fullPath);
}
resolveException(file);
//return request;
resolveException(request, self.issuer);
},

@@ -346,0 +448,0 @@ };

@@ -14,2 +14,11 @@ /**

/**
* @param {string} request
* @return {{resource: string, query: string|null}}
*/
const parseRequest = (request) => {
const [resource, query] = request.split('?');
return { resource, query };
};
const isFunction = (value) => typeof value === 'function';

@@ -41,2 +50,3 @@

pathToPosix,
parseRequest,
isFunction,

@@ -43,0 +53,0 @@ shallowEqual,

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc