You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			256 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			JavaScript
		
	
		
		
			
		
	
	
			256 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			JavaScript
		
	
| 
											9 months ago
										 | /* | ||
|  | 	MIT License http://www.opensource.org/licenses/mit-license.php
 | ||
|  | 	Author Ivan Kopeykin @vankop | ||
|  | */ | ||
|  | 
 | ||
|  | "use strict"; | ||
|  | 
 | ||
|  | const { pathToFileURL } = require("url"); | ||
|  | const ModuleDependencyWarning = require("../ModuleDependencyWarning"); | ||
|  | const { | ||
|  | 	JAVASCRIPT_MODULE_TYPE_AUTO, | ||
|  | 	JAVASCRIPT_MODULE_TYPE_ESM | ||
|  | } = require("../ModuleTypeConstants"); | ||
|  | const Template = require("../Template"); | ||
|  | const BasicEvaluatedExpression = require("../javascript/BasicEvaluatedExpression"); | ||
|  | const { | ||
|  | 	evaluateToIdentifier, | ||
|  | 	toConstantDependency, | ||
|  | 	evaluateToString, | ||
|  | 	evaluateToNumber | ||
|  | } = require("../javascript/JavascriptParserHelpers"); | ||
|  | const memoize = require("../util/memoize"); | ||
|  | const propertyAccess = require("../util/propertyAccess"); | ||
|  | const ConstDependency = require("./ConstDependency"); | ||
|  | 
 | ||
|  | /** @typedef {import("estree").MemberExpression} MemberExpression */ | ||
|  | /** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ | ||
|  | /** @typedef {import("../Compiler")} Compiler */ | ||
|  | /** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ | ||
|  | /** @typedef {import("../NormalModule")} NormalModule */ | ||
|  | /** @typedef {import("../javascript/JavascriptParser")} Parser */ | ||
|  | /** @typedef {import("../javascript/JavascriptParser").Range} Range */ | ||
|  | 
 | ||
|  | const getCriticalDependencyWarning = memoize(() => | ||
|  | 	require("./CriticalDependencyWarning") | ||
|  | ); | ||
|  | 
 | ||
|  | const PLUGIN_NAME = "ImportMetaPlugin"; | ||
|  | 
 | ||
|  | class ImportMetaPlugin { | ||
|  | 	/** | ||
|  | 	 * @param {Compiler} compiler compiler | ||
|  | 	 */ | ||
|  | 	apply(compiler) { | ||
|  | 		compiler.hooks.compilation.tap( | ||
|  | 			PLUGIN_NAME, | ||
|  | 			(compilation, { normalModuleFactory }) => { | ||
|  | 				/** | ||
|  | 				 * @param {NormalModule} module module | ||
|  | 				 * @returns {string} file url | ||
|  | 				 */ | ||
|  | 				const getUrl = module => { | ||
|  | 					return pathToFileURL(module.resource).toString(); | ||
|  | 				}; | ||
|  | 				/** | ||
|  | 				 * @param {Parser} parser parser parser | ||
|  | 				 * @param {JavascriptParserOptions} parserOptions parserOptions | ||
|  | 				 * @returns {void} | ||
|  | 				 */ | ||
|  | 				const parserHandler = (parser, { importMeta }) => { | ||
|  | 					if (importMeta === false) { | ||
|  | 						const { importMetaName } = compilation.outputOptions; | ||
|  | 						if (importMetaName === "import.meta") return; | ||
|  | 
 | ||
|  | 						parser.hooks.expression | ||
|  | 							.for("import.meta") | ||
|  | 							.tap(PLUGIN_NAME, metaProperty => { | ||
|  | 								const dep = new ConstDependency( | ||
|  | 									/** @type {string} */ (importMetaName), | ||
|  | 									/** @type {Range} */ (metaProperty.range) | ||
|  | 								); | ||
|  | 								dep.loc = /** @type {DependencyLocation} */ (metaProperty.loc); | ||
|  | 								parser.state.module.addPresentationalDependency(dep); | ||
|  | 								return true; | ||
|  | 							}); | ||
|  | 						return; | ||
|  | 					} | ||
|  | 
 | ||
|  | 					/// import.meta direct ///
 | ||
|  | 					const webpackVersion = parseInt( | ||
|  | 						require("../../package.json").version, | ||
|  | 						10 | ||
|  | 					); | ||
|  | 					const importMetaUrl = () => | ||
|  | 						JSON.stringify(getUrl(parser.state.module)); | ||
|  | 					const importMetaWebpackVersion = () => JSON.stringify(webpackVersion); | ||
|  | 					/** | ||
|  | 					 * @param {string[]} members members | ||
|  | 					 * @returns {string} error message | ||
|  | 					 */ | ||
|  | 					const importMetaUnknownProperty = members => | ||
|  | 						`${Template.toNormalComment( | ||
|  | 							"unsupported import.meta." + members.join(".") | ||
|  | 						)} undefined${propertyAccess(members, 1)}`;
 | ||
|  | 					parser.hooks.typeof | ||
|  | 						.for("import.meta") | ||
|  | 						.tap( | ||
|  | 							PLUGIN_NAME, | ||
|  | 							toConstantDependency(parser, JSON.stringify("object")) | ||
|  | 						); | ||
|  | 					parser.hooks.expression | ||
|  | 						.for("import.meta") | ||
|  | 						.tap(PLUGIN_NAME, metaProperty => { | ||
|  | 							const referencedPropertiesInDestructuring = | ||
|  | 								parser.destructuringAssignmentPropertiesFor(metaProperty); | ||
|  | 							if (!referencedPropertiesInDestructuring) { | ||
|  | 								const CriticalDependencyWarning = | ||
|  | 									getCriticalDependencyWarning(); | ||
|  | 								parser.state.module.addWarning( | ||
|  | 									new ModuleDependencyWarning( | ||
|  | 										parser.state.module, | ||
|  | 										new CriticalDependencyWarning( | ||
|  | 											"Accessing import.meta directly is unsupported (only property access or destructuring is supported)" | ||
|  | 										), | ||
|  | 										/** @type {DependencyLocation} */ (metaProperty.loc) | ||
|  | 									) | ||
|  | 								); | ||
|  | 								const dep = new ConstDependency( | ||
|  | 									`${ | ||
|  | 										parser.isAsiPosition( | ||
|  | 											/** @type {Range} */ (metaProperty.range)[0] | ||
|  | 										) | ||
|  | 											? ";" | ||
|  | 											: "" | ||
|  | 									}({})`,
 | ||
|  | 									/** @type {Range} */ (metaProperty.range) | ||
|  | 								); | ||
|  | 								dep.loc = /** @type {DependencyLocation} */ (metaProperty.loc); | ||
|  | 								parser.state.module.addPresentationalDependency(dep); | ||
|  | 								return true; | ||
|  | 							} | ||
|  | 
 | ||
|  | 							let str = ""; | ||
|  | 							for (const prop of referencedPropertiesInDestructuring) { | ||
|  | 								switch (prop) { | ||
|  | 									case "url": | ||
|  | 										str += `url: ${importMetaUrl()},`; | ||
|  | 										break; | ||
|  | 									case "webpack": | ||
|  | 										str += `webpack: ${importMetaWebpackVersion()},`; | ||
|  | 										break; | ||
|  | 									default: | ||
|  | 										str += `[${JSON.stringify( | ||
|  | 											prop | ||
|  | 										)}]: ${importMetaUnknownProperty([prop])},`;
 | ||
|  | 										break; | ||
|  | 								} | ||
|  | 							} | ||
|  | 							const dep = new ConstDependency( | ||
|  | 								`({${str}})`, | ||
|  | 								/** @type {Range} */ (metaProperty.range) | ||
|  | 							); | ||
|  | 							dep.loc = /** @type {DependencyLocation} */ (metaProperty.loc); | ||
|  | 							parser.state.module.addPresentationalDependency(dep); | ||
|  | 							return true; | ||
|  | 						}); | ||
|  | 					parser.hooks.evaluateTypeof | ||
|  | 						.for("import.meta") | ||
|  | 						.tap(PLUGIN_NAME, evaluateToString("object")); | ||
|  | 					parser.hooks.evaluateIdentifier.for("import.meta").tap( | ||
|  | 						PLUGIN_NAME, | ||
|  | 						evaluateToIdentifier("import.meta", "import.meta", () => [], true) | ||
|  | 					); | ||
|  | 
 | ||
|  | 					/// import.meta.url ///
 | ||
|  | 					parser.hooks.typeof | ||
|  | 						.for("import.meta.url") | ||
|  | 						.tap( | ||
|  | 							PLUGIN_NAME, | ||
|  | 							toConstantDependency(parser, JSON.stringify("string")) | ||
|  | 						); | ||
|  | 					parser.hooks.expression | ||
|  | 						.for("import.meta.url") | ||
|  | 						.tap(PLUGIN_NAME, expr => { | ||
|  | 							const dep = new ConstDependency( | ||
|  | 								importMetaUrl(), | ||
|  | 								/** @type {Range} */ (expr.range) | ||
|  | 							); | ||
|  | 							dep.loc = /** @type {DependencyLocation} */ (expr.loc); | ||
|  | 							parser.state.module.addPresentationalDependency(dep); | ||
|  | 							return true; | ||
|  | 						}); | ||
|  | 					parser.hooks.evaluateTypeof | ||
|  | 						.for("import.meta.url") | ||
|  | 						.tap(PLUGIN_NAME, evaluateToString("string")); | ||
|  | 					parser.hooks.evaluateIdentifier | ||
|  | 						.for("import.meta.url") | ||
|  | 						.tap(PLUGIN_NAME, expr => { | ||
|  | 							return new BasicEvaluatedExpression() | ||
|  | 								.setString(getUrl(parser.state.module)) | ||
|  | 								.setRange(/** @type {Range} */ (expr.range)); | ||
|  | 						}); | ||
|  | 
 | ||
|  | 					/// import.meta.webpack ///
 | ||
|  | 					parser.hooks.typeof | ||
|  | 						.for("import.meta.webpack") | ||
|  | 						.tap( | ||
|  | 							PLUGIN_NAME, | ||
|  | 							toConstantDependency(parser, JSON.stringify("number")) | ||
|  | 						); | ||
|  | 					parser.hooks.expression | ||
|  | 						.for("import.meta.webpack") | ||
|  | 						.tap( | ||
|  | 							PLUGIN_NAME, | ||
|  | 							toConstantDependency(parser, importMetaWebpackVersion()) | ||
|  | 						); | ||
|  | 					parser.hooks.evaluateTypeof | ||
|  | 						.for("import.meta.webpack") | ||
|  | 						.tap(PLUGIN_NAME, evaluateToString("number")); | ||
|  | 					parser.hooks.evaluateIdentifier | ||
|  | 						.for("import.meta.webpack") | ||
|  | 						.tap(PLUGIN_NAME, evaluateToNumber(webpackVersion)); | ||
|  | 
 | ||
|  | 					/// Unknown properties ///
 | ||
|  | 					parser.hooks.unhandledExpressionMemberChain | ||
|  | 						.for("import.meta") | ||
|  | 						.tap(PLUGIN_NAME, (expr, members) => { | ||
|  | 							const dep = new ConstDependency( | ||
|  | 								importMetaUnknownProperty(members), | ||
|  | 								/** @type {Range} */ (expr.range) | ||
|  | 							); | ||
|  | 							dep.loc = /** @type {DependencyLocation} */ (expr.loc); | ||
|  | 							parser.state.module.addPresentationalDependency(dep); | ||
|  | 							return true; | ||
|  | 						}); | ||
|  | 					parser.hooks.evaluate | ||
|  | 						.for("MemberExpression") | ||
|  | 						.tap(PLUGIN_NAME, expression => { | ||
|  | 							const expr = /** @type {MemberExpression} */ (expression); | ||
|  | 							if ( | ||
|  | 								expr.object.type === "MetaProperty" && | ||
|  | 								expr.object.meta.name === "import" && | ||
|  | 								expr.object.property.name === "meta" && | ||
|  | 								expr.property.type === | ||
|  | 									(expr.computed ? "Literal" : "Identifier") | ||
|  | 							) { | ||
|  | 								return new BasicEvaluatedExpression() | ||
|  | 									.setUndefined() | ||
|  | 									.setRange(/** @type {Range} */ (expr.range)); | ||
|  | 							} | ||
|  | 						}); | ||
|  | 				}; | ||
|  | 
 | ||
|  | 				normalModuleFactory.hooks.parser | ||
|  | 					.for(JAVASCRIPT_MODULE_TYPE_AUTO) | ||
|  | 					.tap(PLUGIN_NAME, parserHandler); | ||
|  | 				normalModuleFactory.hooks.parser | ||
|  | 					.for(JAVASCRIPT_MODULE_TYPE_ESM) | ||
|  | 					.tap(PLUGIN_NAME, parserHandler); | ||
|  | 			} | ||
|  | 		); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | module.exports = ImportMetaPlugin; |