Skip to content

Commit a6b8d2d

Browse files
committedJun 3, 2018
feat: Use jsdoc for static typing
1 parent ee6a165 commit a6b8d2d

File tree

7 files changed

+258
-33
lines changed

7 files changed

+258
-33
lines changed
 

‎index.d.ts

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
2+
/**
3+
* The plugin options
4+
*/
5+
type HtmlWebpackPluginOptions = {
6+
/**
7+
* The title to use for the generated HTML document
8+
*/
9+
title: string,
10+
/**
11+
* `webpack` require path to the template.
12+
* @see https://github.com/jantimon/html-webpack-plugin/blob/master/docs/template-option.md
13+
*/
14+
template: string,
15+
/**
16+
*
17+
*/
18+
templateContent: string | (() => string),
19+
/**
20+
* Allows to overwrite the parameters used in the template
21+
*/
22+
templateParameters:
23+
false // Pass an empty object to the template function
24+
| ((compilation: any, assets, options: HtmlWebpackPluginOptions) => {})
25+
| Object
26+
/**
27+
* The file to write the HTML to.
28+
* Defaults to `index.html`.
29+
* Supports subdirectories eg: `assets/admin.html`
30+
*/
31+
filename: string,
32+
/**
33+
* If `true` then append a unique `webpack` compilation hash to all included scripts and CSS files.
34+
* This is useful for cache busting
35+
*/
36+
hash: boolean,
37+
/**
38+
* Inject all assets into the given `template` or `templateContent`.
39+
*/
40+
inject: false // Don't inject scripts
41+
| true // Inject scripts into body
42+
| 'body' // Inject scripts into body
43+
| 'head' // Inject scripts into head
44+
/**
45+
* Path to the favicon icon
46+
*/
47+
favicon: false | string,
48+
/**
49+
* HTML Minification options
50+
* @https://github.com/kangax/html-minifier#options-quick-reference
51+
*/
52+
minify: boolean | {},
53+
cache: boolean,
54+
/**
55+
* Render errors into the HTML page
56+
*/
57+
showErrors: boolean,
58+
/**
59+
* List all entries which should be injected
60+
*/
61+
chunks: 'all' | string[],
62+
/**
63+
* List all entries which should not be injeccted
64+
*/
65+
excludeChunks: string[],
66+
chunksSortMode: 'auto' | 'manual' | (((entryNameA: string, entryNameB: string) => number)),
67+
/**
68+
* Inject meta tags
69+
*/
70+
meta: false // Disable injection
71+
| {
72+
[name: string]: string // name content pair e.g. {viewport: 'width=device-width, initial-scale=1, shrink-to-fit=no'}`
73+
| {[attributeName: string]: string|boolean} // custom properties e.g. { name:"viewport" content:"width=500, initial-scale=1" }
74+
},
75+
/**
76+
* Enforce self closing tags e.g. <link />
77+
*/
78+
xhtml: boolean
79+
}
80+
81+
/**
82+
* A tag element according to the htmlWebpackPlugin object notation
83+
*/
84+
interface HtmlTagObject {
85+
/**
86+
* Attributes of the html tag
87+
* E.g. `{'disabled': true, 'value': 'demo'}`
88+
*/
89+
attributes: {
90+
[attributeName: string]: string|boolean
91+
},
92+
/**
93+
* Wether this html must not contain innerHTML
94+
* @see https://www.w3.org/TR/html5/syntax.html#void-elements
95+
*/
96+
voidTag: boolean,
97+
/**
98+
* The tag name e.g. `'div'`
99+
*/
100+
tagName: string,
101+
/**
102+
* Inner HTML The
103+
*/
104+
innerHTML?: string
105+
}

‎index.js

+104-30
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1+
// @ts-check
12
'use strict';
23

34
// use Polyfill for util.promisify in node versions < v8
45
const promisify = require('util.promisify');
56

7+
// Import types
8+
/* eslint-disable */
9+
/// <reference path="./index.d.ts" />
10+
/* eslint-enable */
11+
/** @typedef {import("webpack/lib/Compiler.js")} WebpackCompiler */
12+
/** @typedef {import("webpack/lib/Compilation.js")} WebpackCompilation */
13+
614
const vm = require('vm');
715
const fs = require('fs');
816
const _ = require('lodash');
@@ -20,10 +28,17 @@ const fsStatAsync = promisify(fs.stat);
2028
const fsReadFileAsync = promisify(fs.readFile);
2129

2230
class HtmlWebpackPlugin {
31+
/**
32+
* @param {Partial<HtmlWebpackPluginOptions>} options
33+
*/
2334
constructor (options) {
2435
// Default options
36+
/**
37+
* @type {HtmlWebpackPluginOptions}
38+
*/
2539
this.options = _.extend({
2640
template: path.join(__dirname, 'default_index.ejs'),
41+
templateContent: undefined,
2742
templateParameters: templateParametersGenerator,
2843
filename: 'index.html',
2944
hash: false,
@@ -40,8 +55,18 @@ class HtmlWebpackPlugin {
4055
title: 'Webpack App',
4156
xhtml: false
4257
}, options);
58+
// Instance variables to keep caching information
59+
// for multiple builds
60+
this.childCompilerHash = undefined;
61+
this.childCompilationOutputName = undefined;
62+
this.assetJson = undefined;
63+
this.hash = undefined;
4364
}
4465

66+
/**
67+
* apply is called by the webpack main compiler during the start phase
68+
* @param {WebpackCompiler} compiler
69+
*/
4570
apply (compiler) {
4671
const self = this;
4772
let isCompilationCached = false;
@@ -56,8 +81,12 @@ class HtmlWebpackPlugin {
5681
this.options.filename = path.relative(compiler.options.output.path, filename);
5782
}
5883

59-
// setup hooks for webpack 4
84+
// setup hooks for third party plugins
6085
compiler.hooks.compilation.tap('HtmlWebpackPluginHooks', compilation => {
86+
// Setup the hooks only once
87+
if (compilation.hooks.htmlWebpackPluginAlterChunks) {
88+
return;
89+
}
6190
compilation.hooks.htmlWebpackPluginAlterChunks = new SyncWaterfallHook(['chunks', 'objectWithPluginRef']);
6291
compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration = new AsyncSeriesWaterfallHook(['pluginArgs']);
6392
compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing = new AsyncSeriesWaterfallHook(['pluginArgs']);
@@ -86,7 +115,13 @@ class HtmlWebpackPlugin {
86115
});
87116
});
88117

89-
compiler.hooks.emit.tapAsync('HtmlWebpackPlugin', (compilation, callback) => {
118+
compiler.hooks.emit.tapAsync('HtmlWebpackPlugin',
119+
/**
120+
* Hook into the webpack emit phase
121+
* @param {WebpackCompilation} compilation
122+
* @param {() => void} callback
123+
*/
124+
(compilation, callback) => {
90125
const applyPluginsAsyncWaterfall = self.applyPluginsAsyncWaterfall(compilation);
91126
// Get chunks info as json
92127
// Note: we're excluding stuff that we don't need to improve toJson serialization speed.
@@ -175,7 +210,7 @@ class HtmlWebpackPlugin {
175210
const html = result.html;
176211
const assets = result.assets;
177212
// Prepare script and link tags
178-
const assetTags = self.generateHtmlTags(assets);
213+
const assetTags = self.generateHtmlTagObjects(assets);
179214
const pluginArgs = {head: assetTags.head, body: assetTags.body, plugin: self, chunks: chunks, outputName: self.childCompilationOutputName};
180215
// Allow plugins to change the assetTag definitions
181216
return applyPluginsAsyncWaterfall('htmlWebpackPluginAlterAssetTags', true, pluginArgs)
@@ -252,6 +287,8 @@ class HtmlWebpackPlugin {
252287

253288
/**
254289
* Generate the template parameters for the template function
290+
* @param {WebpackCompilation} compilation
291+
*
255292
*/
256293
getTemplateParameters (compilation, assets) {
257294
if (typeof this.options.templateParameters === 'function') {
@@ -266,22 +303,21 @@ class HtmlWebpackPlugin {
266303
/**
267304
* Html post processing
268305
*
269-
* Returns a promise
306+
* @returns Promise<string>
270307
*/
271308
executeTemplate (templateFunction, chunks, assets, compilation) {
272-
return Promise.resolve()
273-
// Template processing
274-
.then(() => {
275-
const templateParams = this.getTemplateParameters(compilation, assets);
276-
let html = '';
277-
try {
278-
html = templateFunction(templateParams);
279-
} catch (e) {
280-
compilation.errors.push(new Error('Template execution failed: ' + e));
281-
return Promise.reject(e);
282-
}
283-
return html;
284-
});
309+
// Template processing
310+
const templateParams = this.getTemplateParameters(compilation, assets);
311+
let html = '';
312+
try {
313+
html = templateFunction(templateParams);
314+
} catch (e) {
315+
compilation.errors.push(new Error('Template execution failed: ' + e));
316+
return Promise.reject(e);
317+
}
318+
// If html is a promise return the promise
319+
// If html is a string turn it into a promise
320+
return Promise.resolve().then(() => html);
285321
}
286322

287323
/**
@@ -290,31 +326,32 @@ class HtmlWebpackPlugin {
290326
* Returns a promise
291327
*/
292328
postProcessHtml (html, assets, assetTags) {
293-
const self = this;
294329
if (typeof html !== 'string') {
295330
return Promise.reject('Expected html to be a string but got ' + JSON.stringify(html));
296331
}
297332
return Promise.resolve()
298333
// Inject
299334
.then(() => {
300-
if (self.options.inject) {
301-
return self.injectAssetsIntoHtml(html, assets, assetTags);
335+
if (this.options.inject) {
336+
return this.injectAssetsIntoHtml(html, assets, assetTags);
302337
} else {
303338
return html;
304339
}
305340
})
306341
// Minify
307342
.then(html => {
308-
if (self.options.minify) {
343+
if (this.options.minify) {
309344
const minify = require('html-minifier').minify;
310-
return minify(html, self.options.minify);
345+
return minify(html, this.options.minify === true ? {} : this.options.minify);
311346
}
312347
return html;
313348
});
314349
}
315350

316351
/*
317352
* Pushes the content of the given filename to the compilation assets
353+
* @param {string} filename
354+
* @param {WebpackCompilation} compilation
318355
*/
319356
addFileToAssets (filename, compilation) {
320357
filename = path.resolve(compilation.compiler.context, filename);
@@ -357,6 +394,9 @@ class HtmlWebpackPlugin {
357394

358395
/**
359396
* Return all chunks from the compilation result which match the exclude and include filters
397+
* @param {any} chunks
398+
* @param {string[]|'all'} includedChunks
399+
* @param {string[]} excludedChunks
360400
*/
361401
filterChunks (chunks, includedChunks, excludedChunks) {
362402
return chunks.filter(chunk => {
@@ -391,15 +431,14 @@ class HtmlWebpackPlugin {
391431
}
392432

393433
htmlWebpackPluginAssets (compilation, chunks) {
394-
const self = this;
395434
const compilationHash = compilation.hash;
396435

397436
// Use the configured public path or build a relative path
398437
let publicPath = typeof compilation.options.output.publicPath !== 'undefined'
399438
// If a hard coded public path exists use it
400439
? compilation.mainTemplate.getPublicPath({hash: compilationHash})
401440
// If no public path was set get a relative url path
402-
: path.relative(path.resolve(compilation.options.output.path, path.dirname(self.childCompilationOutputName)), compilation.options.output.path)
441+
: path.relative(path.resolve(compilation.options.output.path, path.dirname(this.childCompilationOutputName)), compilation.options.output.path)
403442
.split(path.sep).join('/');
404443

405444
if (publicPath.length && publicPath.substr(-1, 1) !== '/') {
@@ -421,8 +460,8 @@ class HtmlWebpackPlugin {
421460

422461
// Append a hash for cache busting
423462
if (this.options.hash) {
424-
assets.manifest = self.appendHash(assets.manifest, compilationHash);
425-
assets.favicon = self.appendHash(assets.favicon, compilationHash);
463+
assets.manifest = this.appendHash(assets.manifest, compilationHash);
464+
assets.favicon = this.appendHash(assets.favicon, compilationHash);
426465
}
427466

428467
for (let i = 0; i < chunks.length; i++) {
@@ -436,7 +475,7 @@ class HtmlWebpackPlugin {
436475

437476
// Append a hash for cache busting
438477
if (this.options.hash) {
439-
chunkFiles = chunkFiles.map(chunkFile => self.appendHash(chunkFile, compilationHash));
478+
chunkFiles = chunkFiles.map(chunkFile => this.appendHash(chunkFile, compilationHash));
440479
}
441480

442481
// Webpack outputs an array for each chunk when using sourcemaps
@@ -464,9 +503,11 @@ class HtmlWebpackPlugin {
464503

465504
/**
466505
* Generate meta tags
506+
* @returns {HtmlTagObject[]}
467507
*/
468508
getMetaTags () {
469-
if (this.options.meta === false) {
509+
const metaOptions = this.options.meta;
510+
if (metaOptions === false) {
470511
return [];
471512
}
472513
// Make tags self-closing in case of xhtml
@@ -493,7 +534,19 @@ class HtmlWebpackPlugin {
493534
}
494535

495536
/**
496-
* Injects the assets into the given html string
537+
* Turns the given asset information into tag object representations
538+
* which is seperated into head and body
539+
*
540+
* @param {{
541+
js: {entryName: string, path: string}[],
542+
css: {entryName: string, path: string}[],
543+
favicon?: string
544+
}} assets
545+
*
546+
* @returns {{
547+
head: HtmlTagObject[],
548+
body: HtmlTagObject[]
549+
}}
497550
*/
498551
generateHtmlTags (assets) {
499552
// Turn script files into script tags
@@ -546,6 +599,17 @@ class HtmlWebpackPlugin {
546599

547600
/**
548601
* Injects the assets into the given html string
602+
*
603+
* @param {string} html
604+
* @param {any} assets
605+
* The input html
606+
* @param {{
607+
head: HtmlTagObject[],
608+
body: HtmlTagObject[]
609+
}} assetTags
610+
* The asset tags to inject
611+
*
612+
* @returns {string}
549613
*/
550614
injectAssetsIntoHtml (html, assets, assetTags) {
551615
const htmlRegExp = /(<html[^>]*>)/i;
@@ -592,7 +656,10 @@ class HtmlWebpackPlugin {
592656
}
593657

594658
/**
595-
* Appends a cache busting hash
659+
* Appends a cache busting hash to the query string of the url
660+
* E.g. http://localhost:8080/ -> http://localhost:8080/?50c9096ba6183fd728eeb065a26ec175
661+
* @param {string} url
662+
* @param {string} hash
596663
*/
597664
appendHash (url, hash) {
598665
if (!url) {
@@ -603,6 +670,10 @@ class HtmlWebpackPlugin {
603670

604671
/**
605672
* Helper to return the absolute template path with a fallback loader
673+
* @param {string} template
674+
* The path to the tempalate e.g. './index.html'
675+
* @param {string} context
676+
* The webpack base resolution path for relative paths e.g. process.cwd()
606677
*/
607678
getFullTemplatePath (template, context) {
608679
// If the template doesn't use a loader use the lodash template loader
@@ -628,6 +699,9 @@ class HtmlWebpackPlugin {
628699
/**
629700
* Helper to promisify compilation.applyPluginsAsyncWaterfall that returns
630701
* a function that helps to merge given plugin arguments with processed ones
702+
*
703+
* @param {WebpackCompilation} compilation
704+
*
631705
*/
632706
applyPluginsAsyncWaterfall (compilation) {
633707
return (eventName, requiresResult, pluginArgs) => {

‎lib/chunksorter.js

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
// @ts-check
12
'use strict';
23

4+
// Import webpack types using commonjs
5+
// As we use only the type we have to prevent warnings about unused varaibles
6+
/* eslint-disable */
7+
const WebpackCompilation = require('webpack/lib/Compilation');
8+
/* eslint-enable */
39
/**
410
* Performs identity mapping (no-sort).
511
* @param {Array} chunks the chunks to sort

‎lib/errors.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
// @ts-nocheck
12
'use strict';
23
const PrettyError = require('pretty-error');
34
const prettyError = new PrettyError();
45
prettyError.withoutColors();
5-
prettyError.skipPackage(['html-plugin-evaluation']);
6+
prettyError.skipPackage('html-plugin-evaluation');
67
prettyError.skipNodeFiles();
78
prettyError.skip(function (traceLine) {
89
return traceLine.path === 'html-plugin-evaluation';

‎lib/loader.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* This loader renders the template with underscore if no other loader was found */
2+
// @ts-nocheck
23
'use strict';
3-
44
const _ = require('lodash');
55
const loaderUtils = require('loader-utils');
66

‎package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
],
1313
"scripts": {
1414
"pretest": "semistandard",
15+
"posttest": "tsc --pretty",
1516
"commit": "git-cz",
1617
"build-examples": "node examples/build-examples.js",
1718
"test": "jasmine",
@@ -23,6 +24,7 @@
2324
]
2425
},
2526
"devDependencies": {
27+
"@types/node": "10.1.1",
2628
"appcache-webpack-plugin": "^1.3.0",
2729
"commitizen": "2.9.6",
2830
"css-loader": "^0.26.1",
@@ -39,9 +41,10 @@
3941
"semistandard": "8.0.0",
4042
"standard-version": "^4.3.0",
4143
"style-loader": "^0.13.1",
44+
"typescript": "2.9.0-dev.20180518",
4245
"underscore-template-loader": "^0.7.3",
4346
"url-loader": "^0.5.7",
44-
"webpack": "^4.0.0",
47+
"webpack": "4.8.3",
4548
"webpack-cli": "2.0.12",
4649
"webpack-recompilation-simulator": "^1.3.0"
4750
},

‎tsconfig.json

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"compilerOptions": {
3+
/* Basic Options */
4+
"allowJs": true, /* Allow javascript files to be compiled. */
5+
"checkJs": true, /* Report errors in .js files. */
6+
"noEmit": true, /* Do not emit outputs. */
7+
"lib": ["es2017"],
8+
9+
/* Strict Type-Checking Options */
10+
"strict": false, /* Enable all strict type-checking options. */
11+
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
12+
// "strictNullChecks": true, /* Enable strict null checks. */
13+
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
14+
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
15+
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
16+
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
17+
18+
/* Additional Checks */
19+
// "noUnusedLocals": true, /* Report errors on unused locals. */
20+
// "noUnusedParameters": true, /* Report errors on unused parameters. */
21+
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
22+
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
23+
24+
/* Module Resolution Options */
25+
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
26+
"allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
27+
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
28+
},
29+
"types": ["node"],
30+
"exclude": [
31+
"node_modules",
32+
"spec",
33+
"examples",
34+
"dist"
35+
]
36+
}

0 commit comments

Comments
 (0)
Please sign in to comment.