init
This commit is contained in:
commit
ee51adf6c2
9 changed files with 1462 additions and 0 deletions
12
.eslintrc.js
Normal file
12
.eslintrc.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
node: true,
|
||||
es6: true,
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
},
|
||||
extends: [
|
||||
'@sup39/basic',
|
||||
],
|
||||
};
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
node_modules/
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 sup39
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
1
README.md
Normal file
1
README.md
Normal file
|
@ -0,0 +1 @@
|
|||
# HTML Factory Webpack Plugin
|
59
index.d.ts
vendored
Normal file
59
index.d.ts
vendored
Normal file
|
@ -0,0 +1,59 @@
|
|||
export type Assets = {
|
||||
js: string[];
|
||||
css: string[];
|
||||
};
|
||||
export type HtmlFactory = (source: string, assets: Assets) => string | Promise<string>;
|
||||
/**
|
||||
* @typedef {{
|
||||
* js: string[]
|
||||
* css: string[]
|
||||
* }} Assets
|
||||
*
|
||||
* @typedef {(source: string, assets: Assets)=>string|Promise<string>} HtmlFactory
|
||||
*/
|
||||
export class HtmlFactoryWebpackPlugin {
|
||||
/**
|
||||
* @param {HtmlFactory} parser
|
||||
* @param {{
|
||||
* input: string
|
||||
* output: string
|
||||
* chunks?: string[]
|
||||
* excludeChunks?: string[]
|
||||
* jsAssetParser?: (url: string)=>string
|
||||
* cssAssetParser?: (url: string)=>string
|
||||
* noCheckDependencies?: boolean
|
||||
* alwaysEmit?: boolean
|
||||
* }} options
|
||||
*/
|
||||
constructor(parser: HtmlFactory, options: {
|
||||
input: string;
|
||||
output: string;
|
||||
chunks?: string[];
|
||||
excludeChunks?: string[];
|
||||
jsAssetParser?: (url: string) => string;
|
||||
cssAssetParser?: (url: string) => string;
|
||||
noCheckDependencies?: boolean;
|
||||
alwaysEmit?: boolean;
|
||||
});
|
||||
parser: HtmlFactory;
|
||||
options: {
|
||||
input: string;
|
||||
output: string;
|
||||
chunks?: string[];
|
||||
excludeChunks?: string[];
|
||||
jsAssetParser?: (url: string) => string;
|
||||
cssAssetParser?: (url: string) => string;
|
||||
noCheckDependencies?: boolean;
|
||||
alwaysEmit?: boolean;
|
||||
};
|
||||
/** @type {null|number} */
|
||||
prevTime: null | number;
|
||||
prevAssets: {
|
||||
js: any[];
|
||||
css: any[];
|
||||
};
|
||||
/** @param {import('webpack').Compiler} compiler */
|
||||
apply(compiler: import('webpack').Compiler): void;
|
||||
}
|
||||
export declare function makeJsAssetTag(url: string): string;
|
||||
export declare function makeCssAssetTag(url: string): string;
|
105
index.js
Normal file
105
index.js
Normal file
|
@ -0,0 +1,105 @@
|
|||
// @ts-check
|
||||
const webpack = require('webpack');
|
||||
const path = require('path');
|
||||
const fs = require('fs/promises');
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* js: string[]
|
||||
* css: string[]
|
||||
* }} Assets
|
||||
*
|
||||
* @typedef {(source: string, assets: Assets)=>string|Promise<string>} HtmlFactory
|
||||
*/
|
||||
|
||||
class HtmlFactoryWebpackPlugin {
|
||||
/**
|
||||
* @param {HtmlFactory} parser
|
||||
* @param {{
|
||||
* input: string
|
||||
* output: string
|
||||
* chunks?: string[]
|
||||
* excludeChunks?: string[]
|
||||
* jsAssetParser?: (url: string)=>string
|
||||
* cssAssetParser?: (url: string)=>string
|
||||
* noCheckDependencies?: boolean
|
||||
* alwaysEmit?: boolean
|
||||
* }} options
|
||||
*/
|
||||
constructor(parser, options) {
|
||||
this.parser = parser;
|
||||
this.options = {
|
||||
input: path.resolve(options.input),
|
||||
...options,
|
||||
};
|
||||
/** @type {null|number} */
|
||||
this.prevTime = null;
|
||||
this.prevAssets = {js: [], css: []};
|
||||
}
|
||||
|
||||
/** @param {import('webpack').Compiler} compiler */
|
||||
apply(compiler) {
|
||||
compiler.hooks.thisCompilation.tap('HtmlFactoryWebpackPlugin', compilation => {
|
||||
compilation.hooks.processAssets.tapAsync({
|
||||
name: 'HtmlFactoryWebpackPlugin',
|
||||
// after minification and dev tooling is done
|
||||
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE,
|
||||
}, async (compilationAssets, callback) => {
|
||||
const {options, prevTime, prevAssets} = this;
|
||||
const {input, output, noCheckDependencies, alwaysEmit} = options;
|
||||
compilation.fileDependencies.add(this.options.input);
|
||||
|
||||
// skip
|
||||
const tInfo = compiler.fileTimestamps?.get(input);
|
||||
const t = typeof tInfo === 'object' ? tInfo.timestamp : null;
|
||||
const sameTime = !alwaysEmit && prevTime != null && (t == null || t === prevTime);
|
||||
if (sameTime && noCheckDependencies) return callback();
|
||||
|
||||
const chunks = options.chunks ?? compilation.entrypoints.keys();
|
||||
const excludeChunks = new Set(options.excludeChunks ?? []);
|
||||
|
||||
const outdir = path.dirname(output);
|
||||
/** @type {Assets} */
|
||||
const assets = {
|
||||
js: [],
|
||||
css: [],
|
||||
};
|
||||
for (const entry of chunks) {
|
||||
if (excludeChunks.has(entry)) continue;
|
||||
const files = compilation.entrypoints.get(entry).getFiles();
|
||||
for (const file of files) {
|
||||
const ext = file.split('.').slice(-1)[0];
|
||||
if (assets[ext] == null) {
|
||||
console.warn(`Unsupported asset: ${file}`);
|
||||
} else {
|
||||
assets[ext].push(path.relative(outdir, file));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sameAssets = Object.entries(assets).every(([k, arr1]) => {
|
||||
const arr2 = prevAssets[k];
|
||||
return arr2 && arr1.length === arr2.length && arr1.every((e, i) => e===arr2[i]);
|
||||
});
|
||||
if (sameTime && sameAssets) return callback();
|
||||
|
||||
const source = (await fs.readFile(input)).toString();
|
||||
const html = await this.parser(source, assets);
|
||||
compilation.emitAsset(output, new webpack.sources.RawSource(html, false));
|
||||
|
||||
// done
|
||||
if (t != null) this.prevTime = t;
|
||||
this.prevAssets = assets;
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
HtmlFactoryWebpackPlugin,
|
||||
makeJsAssetTag: (/**@type{string}*/url) =>
|
||||
`<script src="${encodeURI(url)}"></script>`,
|
||||
makeCssAssetTag: (/**@type{string}*/url) =>
|
||||
`<link rel="stylesheet" type="text/css" href="${encodeURI(url)}">`,
|
||||
};
|
20
package.json
Normal file
20
package.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "@sup39/html-factory-webpack-plugin",
|
||||
"version": "0.1.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"lint": "eslint index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sup39/eslint-config-basic": "^0.1.5",
|
||||
"@types/node": "^18.7.23",
|
||||
"eslint": "^8.24.0",
|
||||
"typescript": "^4.8.4",
|
||||
"webpack": "^5.74.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"webpack": "^5.74.0"
|
||||
}
|
||||
}
|
10
tsconfig.json
Normal file
10
tsconfig.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"include": ["index.js"],
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"allowJs": true,
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"moduleResolution": "node"
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue