UNPKG

5.36 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Jason Anderson @diurnalist
4*/
5"use strict";
6
7const REGEXP_HASH = /\[hash(?::(\d+))?\]/gi,
8 REGEXP_CHUNKHASH = /\[chunkhash(?::(\d+))?\]/gi,
9 REGEXP_MODULEHASH = /\[modulehash(?::(\d+))?\]/gi,
10 REGEXP_CONTENTHASH = /\[contenthash(?::(\d+))?\]/gi,
11 REGEXP_NAME = /\[name\]/gi,
12 REGEXP_ID = /\[id\]/gi,
13 REGEXP_MODULEID = /\[moduleid\]/gi,
14 REGEXP_FILE = /\[file\]/gi,
15 REGEXP_QUERY = /\[query\]/gi,
16 REGEXP_FILEBASE = /\[filebase\]/gi;
17
18// Using global RegExp for .test is dangerous
19// We use a normal RegExp instead of .test
20const REGEXP_HASH_FOR_TEST = new RegExp(REGEXP_HASH.source, "i"),
21 REGEXP_CHUNKHASH_FOR_TEST = new RegExp(REGEXP_CHUNKHASH.source, "i"),
22 REGEXP_CONTENTHASH_FOR_TEST = new RegExp(REGEXP_CONTENTHASH.source, "i"),
23 REGEXP_NAME_FOR_TEST = new RegExp(REGEXP_NAME.source, "i");
24
25const withHashLength = (replacer, handlerFn) => {
26 const fn = (match, hashLength, ...args) => {
27 const length = hashLength && parseInt(hashLength, 10);
28 if (length && handlerFn) {
29 return handlerFn(length);
30 }
31 const hash = replacer(match, hashLength, ...args);
32 return length ? hash.slice(0, length) : hash;
33 };
34 return fn;
35};
36
37const getReplacer = (value, allowEmpty) => {
38 const fn = (match, ...args) => {
39 // last argument in replacer is the entire input string
40 const input = args[args.length - 1];
41 if (value === null || value === undefined) {
42 if (!allowEmpty) {
43 throw new Error(
44 `Path variable ${match} not implemented in this context: ${input}`
45 );
46 }
47 return "";
48 } else {
49 return `${value}`;
50 }
51 };
52 return fn;
53};
54
55const replacePathVariables = (path, data) => {
56 const chunk = data.chunk;
57 const chunkId = chunk && chunk.id;
58 const chunkName = chunk && (chunk.name || chunk.id);
59 const chunkHash = chunk && (chunk.renderedHash || chunk.hash);
60 const chunkHashWithLength = chunk && chunk.hashWithLength;
61 const contentHashType = data.contentHashType;
62 const contentHash =
63 (chunk && chunk.contentHash && chunk.contentHash[contentHashType]) ||
64 data.contentHash;
65 const contentHashWithLength =
66 (chunk &&
67 chunk.contentHashWithLength &&
68 chunk.contentHashWithLength[contentHashType]) ||
69 data.contentHashWithLength;
70 const module = data.module;
71 const moduleId = module && module.id;
72 const moduleHash = module && (module.renderedHash || module.hash);
73 const moduleHashWithLength = module && module.hashWithLength;
74
75 if (typeof path === "function") {
76 path = path(data);
77 }
78
79 if (
80 data.noChunkHash &&
81 (REGEXP_CHUNKHASH_FOR_TEST.test(path) ||
82 REGEXP_CONTENTHASH_FOR_TEST.test(path))
83 ) {
84 throw new Error(
85 `Cannot use [chunkhash] or [contenthash] for chunk in '${path}' (use [hash] instead)`
86 );
87 }
88
89 return (
90 path
91 .replace(
92 REGEXP_HASH,
93 withHashLength(getReplacer(data.hash), data.hashWithLength)
94 )
95 .replace(
96 REGEXP_CHUNKHASH,
97 withHashLength(getReplacer(chunkHash), chunkHashWithLength)
98 )
99 .replace(
100 REGEXP_CONTENTHASH,
101 withHashLength(getReplacer(contentHash), contentHashWithLength)
102 )
103 .replace(
104 REGEXP_MODULEHASH,
105 withHashLength(getReplacer(moduleHash), moduleHashWithLength)
106 )
107 .replace(REGEXP_ID, getReplacer(chunkId))
108 .replace(REGEXP_MODULEID, getReplacer(moduleId))
109 .replace(REGEXP_NAME, getReplacer(chunkName))
110 .replace(REGEXP_FILE, getReplacer(data.filename))
111 .replace(REGEXP_FILEBASE, getReplacer(data.basename))
112 // query is optional, it's OK if it's in a path but there's nothing to replace it with
113 .replace(REGEXP_QUERY, getReplacer(data.query, true))
114 );
115};
116
117class TemplatedPathPlugin {
118 apply(compiler) {
119 compiler.hooks.compilation.tap("TemplatedPathPlugin", compilation => {
120 const mainTemplate = compilation.mainTemplate;
121
122 mainTemplate.hooks.assetPath.tap(
123 "TemplatedPathPlugin",
124 replacePathVariables
125 );
126
127 mainTemplate.hooks.globalHash.tap(
128 "TemplatedPathPlugin",
129 (chunk, paths) => {
130 const outputOptions = mainTemplate.outputOptions;
131 const publicPath = outputOptions.publicPath || "";
132 const filename = outputOptions.filename || "";
133 const chunkFilename =
134 outputOptions.chunkFilename || outputOptions.filename;
135 if (
136 REGEXP_HASH_FOR_TEST.test(publicPath) ||
137 REGEXP_CHUNKHASH_FOR_TEST.test(publicPath) ||
138 REGEXP_CONTENTHASH_FOR_TEST.test(publicPath) ||
139 REGEXP_NAME_FOR_TEST.test(publicPath)
140 )
141 return true;
142 if (REGEXP_HASH_FOR_TEST.test(filename)) return true;
143 if (REGEXP_HASH_FOR_TEST.test(chunkFilename)) return true;
144 if (REGEXP_HASH_FOR_TEST.test(paths.join("|"))) return true;
145 }
146 );
147
148 mainTemplate.hooks.hashForChunk.tap(
149 "TemplatedPathPlugin",
150 (hash, chunk) => {
151 const outputOptions = mainTemplate.outputOptions;
152 const chunkFilename =
153 outputOptions.chunkFilename || outputOptions.filename;
154 if (REGEXP_CHUNKHASH_FOR_TEST.test(chunkFilename)) {
155 hash.update(JSON.stringify(chunk.getChunkMaps(true).hash));
156 }
157 if (REGEXP_CONTENTHASH_FOR_TEST.test(chunkFilename)) {
158 hash.update(
159 JSON.stringify(
160 chunk.getChunkMaps(true).contentHash.javascript || {}
161 )
162 );
163 }
164 if (REGEXP_NAME_FOR_TEST.test(chunkFilename)) {
165 hash.update(JSON.stringify(chunk.getChunkMaps(true).name));
166 }
167 }
168 );
169 });
170 }
171}
172
173module.exports = TemplatedPathPlugin;