1 | ;
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.PreserveModuleNamePlugin = exports.preserveModuleName = void 0;
|
4 | const path = require("path");
|
5 | const logger_1 = require("./logger");
|
6 | exports.preserveModuleName = Symbol();
|
7 | const TAP_NAME = "Aurelia:PreserveModuleName";
|
8 | const logger = logger_1.createLogger('PreserveModuleNamePlugin');
|
9 | // This plugins preserves the module names of IncludeDependency and
|
10 | // AureliaDependency so that they can be dynamically requested by
|
11 | // aurelia-loader.
|
12 | // All other dependencies are handled by webpack itself and don't
|
13 | // need special treatment.
|
14 | class PreserveModuleNamePlugin {
|
15 | constructor(isDll = false) {
|
16 | this.isDll = isDll;
|
17 | }
|
18 | apply(compiler) {
|
19 | compiler.hooks.compilation.tap(TAP_NAME, compilation => {
|
20 | compilation.hooks.beforeModuleIds.tap(TAP_NAME, $modules => {
|
21 | let modules = Array.from($modules);
|
22 | let { modules: m, extensions: e, alias: a } = compilation.options.resolve;
|
23 | let roots = m;
|
24 | let extensions = e;
|
25 | // if it's not an object, it's pretty hard to guess how to map to common usage of alias
|
26 | // temporarily not handle anything that is not a record of aliases
|
27 | let alias = a == null || a instanceof Array ? {} : a;
|
28 | roots = roots.map(x => path.resolve(x));
|
29 | const normalizers = extensions.map(x => new RegExp(x.replace(/\./g, "\\.") + "$", "i"));
|
30 | // ModuleConcatenationPlugin merges modules into new ConcatenatedModule
|
31 | let modulesBeforeConcat = modules.slice();
|
32 | for (let i = 0; i < modulesBeforeConcat.length; i++) {
|
33 | let m = modulesBeforeConcat[i];
|
34 | // TODO: verify if this still works
|
35 | // ==================================================
|
36 | // We don't `import ConcatenatedModule` and then `m instanceof ConcatenatedModule`
|
37 | // because it was introduced in Webpack 3.0 and we're still compatible with 2.x at the moment.
|
38 | if (m.constructor.name === "ConcatenatedModule")
|
39 | modulesBeforeConcat.splice(i--, 1, ...m["modules"]);
|
40 | }
|
41 | for (let module of getPreservedModules(modules, compilation)) {
|
42 | // Even though it's imported by Aurelia, it's still possible that the module
|
43 | // became the _root_ of a ConcatenatedModule.
|
44 | // We use `constructor.name` rather than `instanceof` for compat. with Webpack 2.
|
45 | let realModule = module;
|
46 | if (module.constructor.name === "ConcatenatedModule")
|
47 | realModule = module["rootModule"];
|
48 | let preserve = realModule[exports.preserveModuleName];
|
49 | let id = typeof preserve === "string" ? preserve : null;
|
50 | // No absolute request to preserve, we try to normalize the module resource
|
51 | if (!id && realModule.resource)
|
52 | id = fixNodeModule(realModule, modulesBeforeConcat) ||
|
53 | makeModuleRelative(roots, realModule.resource) ||
|
54 | aliasRelative(alias, realModule.resource);
|
55 | if (!id)
|
56 | throw new Error(`Can't figure out a normalized module name for ${realModule.rawRequest}, please call PLATFORM.moduleName() somewhere to help.`);
|
57 | // Remove default extensions
|
58 | normalizers.forEach(n => id = id.replace(n, ""));
|
59 | // Keep "async!" in front of code splits proxies, they are used by aurelia-loader
|
60 | if (/^async[?!]/.test(realModule.rawRequest))
|
61 | id = "async!" + id;
|
62 | id = id.replace(/\\/g, "/");
|
63 | if (module.buildMeta) // meta can be null if the module contains errors
|
64 | module.buildMeta["aurelia-id"] = id;
|
65 | if (!this.isDll) {
|
66 | compilation.chunkGraph.setModuleId(module, id);
|
67 | }
|
68 | }
|
69 | });
|
70 | });
|
71 | }
|
72 | }
|
73 | exports.PreserveModuleNamePlugin = PreserveModuleNamePlugin;
|
74 | ;
|
75 | function getPreservedModules(modules, compilation) {
|
76 | return new Set(modules.filter(m => {
|
77 | var _a;
|
78 | // Some modules might have [preserveModuleName] already set, see ConventionDependenciesPlugin.
|
79 | let value = m[exports.preserveModuleName];
|
80 | for (let connection of compilation.moduleGraph.getIncomingConnections(m)) {
|
81 | // todo: verify against commented code below
|
82 | if (!((_a = connection === null || connection === void 0 ? void 0 : connection.dependency) === null || _a === void 0 ? void 0 : _a[exports.preserveModuleName])) {
|
83 | continue;
|
84 | }
|
85 | value = true;
|
86 | let req = removeLoaders(connection.dependency.request);
|
87 | // We try to find an absolute string and set that as the module [preserveModuleName], as it's the best id.
|
88 | if (req && !req.startsWith(".")) {
|
89 | m[exports.preserveModuleName] = req;
|
90 | return true;
|
91 | }
|
92 | }
|
93 | return !!value;
|
94 | }));
|
95 | }
|
96 | function aliasRelative(aliases, resource) {
|
97 | // We consider that aliases point to local folder modules.
|
98 | // For example: `"my-lib": "../my-lib/src"`.
|
99 | // Basically we try to make the resource relative to the alias target,
|
100 | // and if it works we build the id from the alias name.
|
101 | // So file `../my-lib/src/index.js` becomes `my-lib/index.js`.
|
102 | // Note that this only works with aliases pointing to folders, not files.
|
103 | // To have a "default" file in the folder, the following construct works:
|
104 | // alias: { "mod$": "src/index.js", "mod": "src" }
|
105 | if (!aliases)
|
106 | return null;
|
107 | for (let name in aliases) {
|
108 | let target = aliases[name];
|
109 | // TODO:
|
110 | // not sure how to handle anything other than a simple mapping yet
|
111 | // just ignore for now
|
112 | if (typeof target !== 'string')
|
113 | continue;
|
114 | let root = path.resolve(target);
|
115 | let relative = path.relative(root, resource);
|
116 | if (relative.startsWith(".."))
|
117 | continue;
|
118 | name = name.replace(/\$$/, ""); // A trailing $ indicates an exact match in webpack
|
119 | return relative ? name + "/" + relative : name;
|
120 | }
|
121 | return null;
|
122 | }
|
123 | function makeModuleRelative(roots, resource) {
|
124 | for (let root of roots) {
|
125 | let relative = path.relative(root, resource);
|
126 | if (!relative.startsWith(".."))
|
127 | return relative;
|
128 | }
|
129 | return null;
|
130 | }
|
131 | function fixNodeModule(module, allModules) {
|
132 | if (!/\bnode_modules\b/i.test(module.resource))
|
133 | return null;
|
134 | // The problem with node_modules is that often the root of the module is not /node_modules/my-lib
|
135 | // Webpack is going to look for `main` in `project.json` to find where the main file actually is.
|
136 | // And this can of course be configured differently with `resolve.alias`, `resolve.mainFields` & co.
|
137 | // Our best hope is that the file was not required as a relative path, then we can just preserve that.
|
138 | // We just need to be careful with loaders (e.g. async!)
|
139 | let request = removeLoaders(module.rawRequest); // we assume that Aurelia dependencies always have a rawRequest
|
140 | if (!request.startsWith("."))
|
141 | return request;
|
142 | // Otherwise we need to build the relative path from the module root, which as explained above is hard to find.
|
143 | // Ideally we could use webpack resolvers, but they are currently async-only, which can't be used in before-modules-id
|
144 | // See https://github.com/webpack/webpack/issues/1634
|
145 | // Instead, we'll look for the root library module, because it should have been requested somehow and work from there.
|
146 | // Note that the negative lookahead (?!.*node_modules) ensures that we only match the last node_modules/ folder in the path,
|
147 | // in case the package was located in a sub-node_modules (which can occur in special circumstances).
|
148 | // We also need to take care of scoped modules. If the name starts with @ we must keep two parts,
|
149 | // so @corp/bar is the proper module name.
|
150 | let name = /\bnode_modules[\\/](?!.*\bnode_modules\b)((?:@[^\\/]+[\\/])?[^\\/]+)/i.exec(module.resource)[1];
|
151 | if (!name) {
|
152 | logger.log('issue while fixing node modules: not a node module', module.resource);
|
153 | return;
|
154 | }
|
155 | name = name.replace("\\", "/"); // normalize \ to / for scoped modules
|
156 | let entry = allModules.find(m => removeLoaders(m.rawRequest) === name);
|
157 | if (entry)
|
158 | return name + "/" + path.relative(path.dirname(entry.resource), module.resource);
|
159 | // We could not find the raw module. Let's try to find another a more complex entry point.
|
160 | for (let m of allModules) {
|
161 | let req = removeLoaders(m.rawRequest);
|
162 | if (!req || !req.startsWith(name) || !m.resource)
|
163 | continue;
|
164 | let i = m.resource.replace(/\\/g, "/").lastIndexOf(req.substr(name.length));
|
165 | if (i < 0)
|
166 | continue;
|
167 | let root = m.resource.substr(0, i);
|
168 | return name + "/" + path.relative(root, module.resource);
|
169 | }
|
170 | throw new Error("PreserveModuleNamePlugin: Unable to find root of module " + name);
|
171 | }
|
172 | function removeLoaders(request) {
|
173 | // We have to be careful, as it seems that in the allModules.find() call above
|
174 | // some modules might have m.rawRequst === undefined
|
175 | if (!request)
|
176 | return request;
|
177 | let lastBang = request.lastIndexOf("!");
|
178 | return lastBang < 0 ? request : request.substr(lastBang + 1);
|
179 | }
|