1 | const fs = require('fs');
|
2 | const path = require('path');
|
3 | const acorn = require('acorn-dynamic-import').default;
|
4 | const walk = require('acorn/dist/walk');
|
5 | const GoogDependency = require('./dependencies/goog-dependency');
|
6 | const GoogBaseGlobalDependency = require('./dependencies/goog-base-global');
|
7 | const GoogLoaderPrefixDependency = require('./dependencies/goog-loader-prefix-dependency');
|
8 | const GoogLoaderSuffixDependency = require('./dependencies/goog-loader-suffix-dependency');
|
9 | const GoogLoaderEs6PrefixDependency = require('./dependencies/goog-loader-es6-prefix-dependency');
|
10 | const GoogLoaderEs6SuffixDependency = require('./dependencies/goog-loader-es6-suffix-dependency');
|
11 |
|
12 | const PLUGIN = { name: 'ClosureLibraryPlugin' };
|
13 |
|
14 | const isProductionLikeMode = (options) =>
|
15 | options.mode === 'production' || !options.mode;
|
16 |
|
17 | class GoogRequireParserPlugin {
|
18 | constructor(options) {
|
19 | this.options = Object.assign({ deps: [], extraDeps: {} }, options);
|
20 |
|
21 | if (Array.isArray(this.options.deps)) {
|
22 | this.deps = this.options.deps.slice();
|
23 | } else {
|
24 | this.deps = [this.options.deps];
|
25 | }
|
26 |
|
27 | this.basePath = path.resolve(this.options.closureLibraryBase);
|
28 | const baseDir = path.dirname(this.basePath);
|
29 | const googPathsByNamespace = new Map();
|
30 | this.googPathsByNamespace = googPathsByNamespace;
|
31 | const googDepsByPath = new Map();
|
32 |
|
33 | Object.keys(this.options.extraDeps).forEach((namespace) => {
|
34 | this.googPathsByNamespace.set(
|
35 | namespace,
|
36 | this.options.extraDeps[namespace]
|
37 | );
|
38 | });
|
39 |
|
40 | this.deps.forEach((depFilePath) => {
|
41 | const depFileContents = fs.readFileSync(depFilePath, 'utf8');
|
42 | const ast = acorn.parse(depFileContents, {
|
43 | ranges: true,
|
44 | locations: false,
|
45 | ecmaVersion: 2017,
|
46 | plugins: {
|
47 | dynamicImport: true,
|
48 | },
|
49 | });
|
50 | walk.simple(ast, {
|
51 | CallExpression(node) {
|
52 | if (
|
53 | node.callee.type === 'MemberExpression' &&
|
54 | node.callee.object.type === 'Identifier' &&
|
55 | node.callee.object.name === 'goog' &&
|
56 | node.callee.property.type === 'Identifier' &&
|
57 | node.callee.property.name === 'addDependency'
|
58 | ) {
|
59 | const filePath = path.resolve(baseDir, node.arguments[0].value);
|
60 | node.arguments[1].elements.forEach((arg) =>
|
61 | googPathsByNamespace.set(arg.value, filePath)
|
62 | );
|
63 | if (
|
64 | !googDepsByPath.has(filePath) &&
|
65 | node.arguments[2] &&
|
66 | node.arguments[2].elements.length > 0
|
67 | ) {
|
68 | googDepsByPath.set(
|
69 | filePath,
|
70 | node.arguments[2].elements.map((nodeVal) => nodeVal.value)
|
71 | );
|
72 | }
|
73 | }
|
74 | },
|
75 | });
|
76 | });
|
77 | }
|
78 |
|
79 | apply(parser) {
|
80 | const googRequireProvideCallback = (expr) => {
|
81 | if (
|
82 | !parser.state.current.hasDependencies(
|
83 | (dep) => dep.request === this.basePath
|
84 | )
|
85 | ) {
|
86 | this.addGoogDependency(parser, this.basePath, true);
|
87 | }
|
88 |
|
89 |
|
90 | if (expr.callee.property.name === 'provide') {
|
91 | if (
|
92 | !isProductionLikeMode(this.options) &&
|
93 | !parser.state.current.dependencies.find(
|
94 | (dep) => dep instanceof GoogLoaderPrefixDependency
|
95 | )
|
96 | ) {
|
97 | this.addLoaderDependency(parser, false);
|
98 | }
|
99 | return false;
|
100 | }
|
101 |
|
102 | try {
|
103 | const param = expr.arguments[0].value;
|
104 | const modulePath = this.googPathsByNamespace.get(param);
|
105 | if (!modulePath) {
|
106 | parser.state.compilation.warnings.push(
|
107 | new Error(`Unable to locate module for namespace: ${param}`)
|
108 | );
|
109 | return false;
|
110 | }
|
111 | const isRequireType = expr.callee.property.name === 'requireType';
|
112 | this.addGoogDependency(parser, modulePath, false, isRequireType);
|
113 | } catch (e) {
|
114 | parser.state.compilation.errors.push(e);
|
115 | }
|
116 | return false;
|
117 | };
|
118 | parser.hooks.call
|
119 | .for('goog.require')
|
120 | .tap(PLUGIN, googRequireProvideCallback);
|
121 | parser.hooks.call
|
122 | .for('goog.requireType')
|
123 | .tap(PLUGIN, googRequireProvideCallback);
|
124 | parser.hooks.call
|
125 | .for('goog.provide')
|
126 | .tap(PLUGIN, googRequireProvideCallback);
|
127 |
|
128 |
|
129 | if (!isProductionLikeMode(this.options)) {
|
130 | parser.hooks.statement.tap(PLUGIN, (expr) => {
|
131 | if (
|
132 | expr.type === 'VariableDeclaration' &&
|
133 | expr.declarations.length === 1 &&
|
134 | expr.declarations[0].id.name === 'goog' &&
|
135 | parser.state.current.userRequest === this.basePath
|
136 | ) {
|
137 | parser.state.current.addVariable(
|
138 | 'goog',
|
139 | 'window.goog = window.goog || {}',
|
140 | []
|
141 | );
|
142 | parser.state.current.contextArgument = function() {
|
143 | return 'window';
|
144 | };
|
145 | parser.state.current.addDependency(new GoogBaseGlobalDependency());
|
146 | }
|
147 | });
|
148 | parser.hooks.call.for('goog.module').tap(PLUGIN, (expr) => {
|
149 | if (!isProductionLikeMode(this.options)) {
|
150 | if (
|
151 | !parser.state.current.hasDependencies(
|
152 | (dep) => dep.request === this.basePath
|
153 | )
|
154 | ) {
|
155 | this.addGoogDependency(parser, this.basePath);
|
156 | }
|
157 |
|
158 | const prefixDep = parser.state.current.dependencies.find(
|
159 | (dep) => dep instanceof GoogLoaderPrefixDependency
|
160 | );
|
161 | const suffixDep = parser.state.current.dependencies.find(
|
162 | (dep) => dep instanceof GoogLoaderSuffixDependency
|
163 | );
|
164 | if (prefixDep && suffixDep) {
|
165 | prefixDep.isGoogModule = true;
|
166 | suffixDep.isGoogModule = true;
|
167 | } else {
|
168 | this.addLoaderDependency(parser, true);
|
169 | }
|
170 | }
|
171 | });
|
172 | const googModuleDeclareCallback = () => {
|
173 | if (
|
174 | !parser.state.current.hasDependencies(
|
175 | (dep) => dep.request === this.basePath
|
176 | )
|
177 | ) {
|
178 | this.addGoogDependency(parser, this.basePath);
|
179 | }
|
180 |
|
181 | parser.state.current.addVariable(
|
182 | '$jscomp',
|
183 | 'window.$jscomp = window.$jscomp || {}',
|
184 | []
|
185 | );
|
186 |
|
187 | this.addEs6LoaderDependency(parser);
|
188 | };
|
189 | parser.hooks.call
|
190 | .for('goog.module.declareNamespace')
|
191 | .tap(PLUGIN, googModuleDeclareCallback);
|
192 | parser.hooks.call
|
193 | .for('goog.declareModuleId')
|
194 | .tap(PLUGIN, googModuleDeclareCallback);
|
195 |
|
196 | parser.hooks.import.tap(PLUGIN, () => {
|
197 | parser.state.current.addVariable(
|
198 | '$jscomp',
|
199 | 'window.$jscomp = window.$jscomp || {}',
|
200 | []
|
201 | );
|
202 | this.addEs6LoaderDependency(parser);
|
203 | });
|
204 | parser.hooks.export.tap(PLUGIN, () => {
|
205 | parser.state.current.addVariable(
|
206 | '$jscomp',
|
207 | 'window.$jscomp = window.$jscomp || {}',
|
208 | []
|
209 | );
|
210 | this.addEs6LoaderDependency(parser);
|
211 | });
|
212 | }
|
213 | }
|
214 |
|
215 | addGoogDependency(parser, request, addAsBaseJs, isRequireType) {
|
216 |
|
217 |
|
218 | const baseInsertPos = !isProductionLikeMode(this.options) ? -1 : null;
|
219 | parser.state.current.addDependency(
|
220 | new GoogDependency(request, baseInsertPos, addAsBaseJs, isRequireType)
|
221 | );
|
222 | }
|
223 |
|
224 | addLoaderDependency(parser, isModule) {
|
225 | parser.state.current.addDependency(
|
226 | new GoogLoaderPrefixDependency(this.basePath, isModule, 0)
|
227 | );
|
228 | const sourceLength = parser.state.current._source.source().length;
|
229 | parser.state.current.addDependency(
|
230 | new GoogLoaderSuffixDependency(this.basePath, isModule, sourceLength)
|
231 | );
|
232 | }
|
233 |
|
234 | addEs6LoaderDependency(parser) {
|
235 | if (
|
236 | parser.state.current.dependencies.some(
|
237 | (dep) => dep instanceof GoogLoaderEs6PrefixDependency
|
238 | )
|
239 | ) {
|
240 | return;
|
241 | }
|
242 |
|
243 |
|
244 |
|
245 | const baseInsertPos = !isProductionLikeMode(this.options) ? 0 : null;
|
246 | const sourceLength = !isProductionLikeMode(this.options)
|
247 | ? parser.state.current._source.source().length
|
248 | : null;
|
249 | parser.state.current.addDependency(
|
250 | new GoogLoaderEs6PrefixDependency(baseInsertPos)
|
251 | );
|
252 | parser.state.current.addDependency(
|
253 | new GoogLoaderEs6SuffixDependency(sourceLength)
|
254 | );
|
255 | }
|
256 | }
|
257 |
|
258 | module.exports = GoogRequireParserPlugin;
|