1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const fs = require("fs");
|
4 | const path = require("path");
|
5 | const ts = require("typescript");
|
6 | const webpack = require("webpack");
|
7 | const app_utils_1 = require("../utilities/app-utils");
|
8 | const webpack_config_1 = require("../models/webpack-config");
|
9 | const config_1 = require("../models/config");
|
10 | const webpack_1 = require("@ngtools/webpack");
|
11 | const chalk_1 = require("chalk");
|
12 | const denodeify = require("denodeify");
|
13 | const common_tags_1 = require("common-tags");
|
14 | const exists = (p) => Promise.resolve(fs.existsSync(p));
|
15 | const writeFile = denodeify(fs.writeFile);
|
16 | const angularCliPlugins = require('../plugins/webpack');
|
17 | const autoprefixer = require('autoprefixer');
|
18 | const postcssUrl = require('postcss-url');
|
19 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
20 | const HtmlWebpackPlugin = require('html-webpack-plugin');
|
21 | const SilentError = require('silent-error');
|
22 | const Task = require('../ember-cli/lib/models/task');
|
23 | const LoaderOptionsPlugin = webpack.LoaderOptionsPlugin;
|
24 | const ProgressPlugin = require('webpack/lib/ProgressPlugin');
|
25 | exports.pluginArgs = Symbol('plugin-args');
|
26 | exports.postcssArgs = Symbol('postcss-args');
|
27 | const pree2eNpmScript = `webdriver-manager update --standalone false --gecko false --quiet`;
|
28 | class JsonWebpackSerializer {
|
29 | constructor(_root, _dist) {
|
30 | this._root = _root;
|
31 | this._dist = _dist;
|
32 | this.imports = {};
|
33 | this.variableImports = {
|
34 | 'path': 'path'
|
35 | };
|
36 | this.variables = {
|
37 | 'nodeModules': `path.join(process.cwd(), 'node_modules')`,
|
38 | };
|
39 | }
|
40 | _escape(str) {
|
41 | return '\uFF01' + str + '\uFF01';
|
42 | }
|
43 | _serializeRegExp(re) {
|
44 | return this._escape(re.toString());
|
45 | }
|
46 | _serializeFunction(fn) {
|
47 | return this._escape(fn.toString());
|
48 | }
|
49 | _relativePath(of, to) {
|
50 | return this._escape(`path.join(${of}, ${JSON.stringify(to)})`);
|
51 | }
|
52 | _addImport(module, importName) {
|
53 | if (!this.imports[module]) {
|
54 | this.imports[module] = [];
|
55 | }
|
56 | if (this.imports[module].indexOf(importName) == -1) {
|
57 | this.imports[module].push(importName);
|
58 | }
|
59 | }
|
60 | _commonsChunkPluginSerialize(value) {
|
61 | let minChunks = value.minChunks;
|
62 | switch (typeof minChunks) {
|
63 | case 'function':
|
64 | minChunks = this._serializeFunction(value.minChunks);
|
65 | break;
|
66 | }
|
67 | return {
|
68 | name: value.chunkNames,
|
69 | filename: value.filenameTemplate,
|
70 | minChunks,
|
71 | chunks: value.selectedChunks,
|
72 | async: value.async,
|
73 | minSize: value.minSize
|
74 | };
|
75 | }
|
76 | _extractTextPluginSerialize(value) {
|
77 | return {
|
78 | filename: value.filename,
|
79 | disable: value.options.disable
|
80 | };
|
81 | }
|
82 | _aotPluginSerialize(value) {
|
83 | const tsConfigPath = path.relative(this._root, value.options.tsConfigPath);
|
84 | const basePath = path.dirname(tsConfigPath);
|
85 | return Object.assign({}, value.options, {
|
86 | tsConfigPath,
|
87 | mainPath: path.relative(value.basePath, value.options.mainPath),
|
88 | hostReplacementPaths: Object.keys(value.options.hostReplacementPaths)
|
89 | .reduce((acc, key) => {
|
90 | const replacementPath = value.options.hostReplacementPaths[key];
|
91 | key = path.relative(basePath, key);
|
92 | acc[key] = path.relative(basePath, replacementPath);
|
93 | return acc;
|
94 | }, {}),
|
95 | exclude: Array.isArray(value.options.exclude)
|
96 | ? value.options.exclude.map((p) => {
|
97 | return p.startsWith('/') ? path.relative(value.basePath, p) : p;
|
98 | })
|
99 | : value.options.exclude
|
100 | });
|
101 | }
|
102 | _htmlWebpackPlugin(value) {
|
103 | const chunksSortMode = value.options.chunksSortMode;
|
104 | this.variables['entryPoints'] = JSON.stringify(chunksSortMode.entryPoints);
|
105 | return Object.assign({}, value.options, {
|
106 | template: './' + path.relative(this._root, value.options.template),
|
107 | filename: './' + path.relative(this._dist, value.options.filename),
|
108 | chunksSortMode: this._serializeFunction(chunksSortMode)
|
109 | });
|
110 | }
|
111 | _loaderOptionsPlugin(plugin) {
|
112 | return Object.assign({}, plugin.options, {
|
113 | test: plugin.options.test instanceof RegExp
|
114 | ? this._serializeRegExp(plugin.options.test)
|
115 | : undefined,
|
116 | options: Object.assign({}, plugin.options.options, {
|
117 | context: '',
|
118 | postcss: plugin.options.options.postcss.map((x) => {
|
119 | if (x && x.toString() == autoprefixer()) {
|
120 | this.variableImports['autoprefixer'] = 'autoprefixer';
|
121 | return this._escape('autoprefixer()');
|
122 | }
|
123 | else if (x && x.toString() == postcssUrl()) {
|
124 | this.variableImports['postcss-url'] = 'postcssUrl';
|
125 | let args = '';
|
126 | if (x[exports.postcssArgs] && x[exports.postcssArgs].url) {
|
127 | this.variables['baseHref'] = JSON.stringify(x[exports.postcssArgs].baseHref);
|
128 | this.variables['deployUrl'] = JSON.stringify(x[exports.postcssArgs].deployUrl);
|
129 | args = `{"url": ${x[exports.postcssArgs].url.toString()}}`;
|
130 | }
|
131 | return this._escape(`postcssUrl(${args})`);
|
132 | }
|
133 | else if (x && x.postcssPlugin == 'cssnano') {
|
134 | this.variableImports['cssnano'] = 'cssnano';
|
135 | return this._escape('cssnano({ safe: true, autoprefixer: false })');
|
136 | }
|
137 | else {
|
138 | if (typeof x == 'function') {
|
139 | return this._serializeFunction(x);
|
140 | }
|
141 | else {
|
142 | return x;
|
143 | }
|
144 | }
|
145 | })
|
146 | })
|
147 | });
|
148 | }
|
149 | _definePlugin(plugin) {
|
150 | return plugin.definitions;
|
151 | }
|
152 | _pluginsReplacer(plugins) {
|
153 | return plugins.map(plugin => {
|
154 | let args = plugin.options || undefined;
|
155 | switch (plugin.constructor) {
|
156 | case ProgressPlugin:
|
157 | this.variableImports['webpack/lib/ProgressPlugin'] = 'ProgressPlugin';
|
158 | break;
|
159 | case webpack.NoEmitOnErrorsPlugin:
|
160 | this._addImport('webpack', 'NoEmitOnErrorsPlugin');
|
161 | break;
|
162 | case webpack.HashedModuleIdsPlugin:
|
163 | this._addImport('webpack', 'HashedModuleIdsPlugin');
|
164 | break;
|
165 | case webpack.optimize.UglifyJsPlugin:
|
166 | this._addImport('webpack.optimize', 'UglifyJsPlugin');
|
167 | break;
|
168 | case angularCliPlugins.BaseHrefWebpackPlugin:
|
169 | case angularCliPlugins.GlobCopyWebpackPlugin:
|
170 | case angularCliPlugins.SuppressExtractedTextChunksWebpackPlugin:
|
171 | this._addImport('@angular/cli/plugins/webpack', plugin.constructor.name);
|
172 | break;
|
173 | case webpack.optimize.CommonsChunkPlugin:
|
174 | args = this._commonsChunkPluginSerialize(plugin);
|
175 | this._addImport('webpack.optimize', 'CommonsChunkPlugin');
|
176 | break;
|
177 | case ExtractTextPlugin:
|
178 | args = this._extractTextPluginSerialize(plugin);
|
179 | this.variableImports['extract-text-webpack-plugin'] = 'ExtractTextPlugin';
|
180 | break;
|
181 | case webpack_1.AotPlugin:
|
182 | args = this._aotPluginSerialize(plugin);
|
183 | this._addImport('@ngtools/webpack', 'AotPlugin');
|
184 | break;
|
185 | case HtmlWebpackPlugin:
|
186 | args = this._htmlWebpackPlugin(plugin);
|
187 | this.variableImports['html-webpack-plugin'] = 'HtmlWebpackPlugin';
|
188 | break;
|
189 | case LoaderOptionsPlugin:
|
190 | args = this._loaderOptionsPlugin(plugin);
|
191 | this._addImport('webpack', 'LoaderOptionsPlugin');
|
192 | break;
|
193 | case webpack.DefinePlugin:
|
194 | args = this._definePlugin(plugin);
|
195 | this._addImport('webpack', 'DefinePlugin');
|
196 | break;
|
197 | default:
|
198 | if (plugin.constructor.name == 'AngularServiceWorkerPlugin') {
|
199 | this._addImport('@angular/service-worker/build/webpack', plugin.constructor.name);
|
200 | }
|
201 | break;
|
202 | }
|
203 | const argsSerialized = JSON.stringify(args, (k, v) => this._replacer(k, v), 2) || '';
|
204 | return `\uFF02${plugin.constructor.name}(${argsSerialized})\uFF02`;
|
205 | });
|
206 | }
|
207 | _resolveReplacer(value) {
|
208 | return Object.assign({}, value, {
|
209 | modules: value.modules.map((x) => './' + path.relative(this._root, x))
|
210 | });
|
211 | }
|
212 | _outputReplacer(value) {
|
213 | return Object.assign({}, value, {
|
214 | path: this._relativePath('process.cwd()', path.relative(this._root, value.path))
|
215 | });
|
216 | }
|
217 | _path(l) {
|
218 | return l.split('!').map(x => {
|
219 | return path.isAbsolute(x) ? './' + path.relative(this._root, x) : x;
|
220 | }).join('!');
|
221 | }
|
222 | _entryReplacer(value) {
|
223 | const newValue = Object.assign({}, value);
|
224 | for (const key of Object.keys(newValue)) {
|
225 | newValue[key] = newValue[key].map((l) => this._path(l));
|
226 | }
|
227 | return newValue;
|
228 | }
|
229 | _loaderReplacer(loader) {
|
230 | if (typeof loader == 'string') {
|
231 | if (loader.match(/\/node_modules\/extract-text-webpack-plugin\//)) {
|
232 | return 'extract-text-webpack-plugin';
|
233 | }
|
234 | else if (loader.match(/@ngtools\/webpack\/src\/index.ts/)) {
|
235 |
|
236 | }
|
237 | }
|
238 | else {
|
239 | if (loader.loader) {
|
240 | loader.loader = this._loaderReplacer(loader.loader);
|
241 | }
|
242 | }
|
243 | return loader;
|
244 | }
|
245 | _ruleReplacer(value) {
|
246 | const replaceExcludeInclude = (v) => {
|
247 | if (typeof v == 'object') {
|
248 | if (v.constructor == RegExp) {
|
249 | return this._serializeRegExp(v);
|
250 | }
|
251 | return v;
|
252 | }
|
253 | else if (typeof v == 'string') {
|
254 | if (v === path.join(this._root, 'node_modules')) {
|
255 | return this._serializeRegExp(/\/node_modules\//);
|
256 | }
|
257 | return this._relativePath('process.cwd()', path.relative(this._root, v));
|
258 | }
|
259 | else {
|
260 | return v;
|
261 | }
|
262 | };
|
263 | if (value[exports.pluginArgs]) {
|
264 | return {
|
265 | include: Array.isArray(value.include)
|
266 | ? value.include.map((x) => replaceExcludeInclude(x))
|
267 | : replaceExcludeInclude(value.include),
|
268 | test: this._serializeRegExp(value.test),
|
269 | loaders: this._escape(`ExtractTextPlugin.extract(${JSON.stringify(value[exports.pluginArgs], null, 2)})`)
|
270 | };
|
271 | }
|
272 | if (value.loaders) {
|
273 | value.loaders = value.loaders.map((loader) => this._loaderReplacer(loader));
|
274 | }
|
275 | if (value.loader) {
|
276 | value.loader = this._loaderReplacer(value.loader);
|
277 | }
|
278 | if (value.exclude) {
|
279 | value.exclude = Array.isArray(value.exclude)
|
280 | ? value.exclude.map((x) => replaceExcludeInclude(x))
|
281 | : replaceExcludeInclude(value.exclude);
|
282 | }
|
283 | if (value.include) {
|
284 | value.include = Array.isArray(value.include)
|
285 | ? value.include.map((x) => replaceExcludeInclude(x))
|
286 | : replaceExcludeInclude(value.include);
|
287 | }
|
288 | return value;
|
289 | }
|
290 | _moduleReplacer(value) {
|
291 | return Object.assign({}, value, {
|
292 | rules: value.rules && value.rules.map((x) => this._ruleReplacer(x))
|
293 | });
|
294 | }
|
295 | _replacer(_key, value) {
|
296 | if (value === undefined) {
|
297 | return value;
|
298 | }
|
299 | if (value === null) {
|
300 | return null;
|
301 | }
|
302 | if (value.constructor === RegExp) {
|
303 | return this._serializeRegExp(value);
|
304 | }
|
305 | return value;
|
306 | }
|
307 | serialize(config) {
|
308 |
|
309 | config['plugins'] = this._pluginsReplacer(config['plugins']);
|
310 | config['resolve'] = this._resolveReplacer(config['resolve']);
|
311 | config['resolveLoader'] = this._resolveReplacer(config['resolveLoader']);
|
312 | config['entry'] = this._entryReplacer(config['entry']);
|
313 | config['output'] = this._outputReplacer(config['output']);
|
314 | config['module'] = this._moduleReplacer(config['module']);
|
315 | config['context'] = undefined;
|
316 | return JSON.stringify(config, (k, v) => this._replacer(k, v), 2)
|
317 | .replace(/"\uFF01(.*?)\uFF01"/g, (_, v) => {
|
318 | return JSON.parse(`"${v}"`);
|
319 | })
|
320 | .replace(/(\s*)(.*?)"\uFF02(.*?)\uFF02"(,?).*/g, (_, indent, key, value, comma) => {
|
321 | const ctor = JSON.parse(`"${value}"`).split(/\n+/g).join(indent);
|
322 | return `${indent}${key}new ${ctor}${comma}`;
|
323 | })
|
324 | .replace(/"\uFF01(.*?)\uFF01"/g, (_, v) => {
|
325 | return JSON.parse(`"${v}"`);
|
326 | });
|
327 | }
|
328 | generateVariables() {
|
329 | let variableOutput = '';
|
330 | Object.keys(this.variableImports)
|
331 | .forEach((key) => {
|
332 | const [module, name] = key.split(/\./);
|
333 | variableOutput += `const ${this.variableImports[key]} = require` + `('${module}')`;
|
334 | if (name) {
|
335 | variableOutput += '.' + name;
|
336 | }
|
337 | variableOutput += ';\n';
|
338 | });
|
339 | variableOutput += '\n';
|
340 | Object.keys(this.imports)
|
341 | .forEach((key) => {
|
342 | const [module, name] = key.split(/\./);
|
343 | variableOutput += `const { ${this.imports[key].join(', ')} } = require` + `('${module}')`;
|
344 | if (name) {
|
345 | variableOutput += '.' + name;
|
346 | }
|
347 | variableOutput += ';\n';
|
348 | });
|
349 | variableOutput += '\n';
|
350 | Object.keys(this.variables)
|
351 | .forEach((key) => {
|
352 | variableOutput += `const ${key} = ${this.variables[key]};\n`;
|
353 | });
|
354 | variableOutput += '\n\n';
|
355 | return variableOutput;
|
356 | }
|
357 | }
|
358 | exports.default = Task.extend({
|
359 | run: function (runTaskOptions) {
|
360 | const project = this.cliProject;
|
361 | const cliConfig = config_1.CliConfig.fromProject();
|
362 | const config = cliConfig.config;
|
363 | const appConfig = app_utils_1.getAppFromConfig(config.apps, runTaskOptions.app);
|
364 | const tsConfigPath = path.join(process.cwd(), appConfig.root, appConfig.tsconfig);
|
365 | const outputPath = runTaskOptions.outputPath || appConfig.outDir;
|
366 | const force = runTaskOptions.force;
|
367 | if (project.root === outputPath) {
|
368 | throw new SilentError('Output path MUST not be project root directory!');
|
369 | }
|
370 | const webpackConfig = new webpack_config_1.NgCliWebpackConfig(runTaskOptions, appConfig).buildConfig();
|
371 | const serializer = new JsonWebpackSerializer(process.cwd(), outputPath);
|
372 | const output = serializer.serialize(webpackConfig);
|
373 | const webpackConfigStr = `${serializer.generateVariables()}\n\nmodule.exports = ${output};\n`;
|
374 | return Promise.resolve()
|
375 | .then(() => exists('webpack.config.js'))
|
376 | .then(webpackConfigExists => {
|
377 | if (webpackConfigExists && !force) {
|
378 | throw new SilentError('The webpack.config.js file already exists.');
|
379 | }
|
380 | })
|
381 | .then(() => ts.sys.readFile('package.json'))
|
382 | .then((packageJson) => JSON.parse(packageJson))
|
383 | .then((packageJson) => {
|
384 | const scripts = packageJson['scripts'];
|
385 | if (scripts['build'] && scripts['build'] !== 'ng build' && !force) {
|
386 | throw new SilentError(common_tags_1.oneLine `
|
387 | Your package.json scripts needs to not contain a build script as it will be overwritten.
|
388 | `);
|
389 | }
|
390 | if (scripts['start'] && scripts['start'] !== 'ng serve' && !force) {
|
391 | throw new SilentError(common_tags_1.oneLine `
|
392 | Your package.json scripts needs to not contain a start script as it will be overwritten.
|
393 | `);
|
394 | }
|
395 | if (scripts['pree2e'] && scripts['pree2e'] !== pree2eNpmScript && !force) {
|
396 | throw new SilentError(common_tags_1.oneLine `
|
397 | Your package.json scripts needs to not contain a pree2e script as it will be
|
398 | overwritten.
|
399 | `);
|
400 | }
|
401 | if (scripts['e2e'] && scripts['e2e'] !== 'ng e2e' && !force) {
|
402 | throw new SilentError(common_tags_1.oneLine `
|
403 | Your package.json scripts needs to not contain a e2e script as it will be overwritten.
|
404 | `);
|
405 | }
|
406 | if (scripts['test'] && scripts['test'] !== 'ng test' && !force) {
|
407 | throw new SilentError(common_tags_1.oneLine `
|
408 | Your package.json scripts needs to not contain a test script as it will be overwritten.
|
409 | `);
|
410 | }
|
411 | packageJson['scripts']['build'] = 'webpack';
|
412 | packageJson['scripts']['start'] = 'webpack-dev-server';
|
413 | packageJson['scripts']['test'] = 'karma start ./karma.conf.js';
|
414 | packageJson['scripts']['pree2e'] = pree2eNpmScript;
|
415 | packageJson['scripts']['e2e'] = 'protractor ./protractor.conf.js';
|
416 |
|
417 | const ourPackageJson = require('../package.json');
|
418 | packageJson['devDependencies']['webpack-dev-server']
|
419 | = ourPackageJson['dependencies']['webpack-dev-server'];
|
420 |
|
421 | [
|
422 | 'autoprefixer',
|
423 | 'css-loader',
|
424 | 'cssnano',
|
425 | 'exports-loader',
|
426 | 'file-loader',
|
427 | 'json-loader',
|
428 | 'karma-sourcemap-loader',
|
429 | 'less-loader',
|
430 | 'postcss-loader',
|
431 | 'postcss-url',
|
432 | 'raw-loader',
|
433 | 'sass-loader',
|
434 | 'script-loader',
|
435 | 'source-map-loader',
|
436 | 'istanbul-instrumenter-loader',
|
437 | 'style-loader',
|
438 | 'stylus-loader',
|
439 | 'url-loader',
|
440 | ].forEach((packageName) => {
|
441 | packageJson['devDependencies'][packageName] = ourPackageJson['dependencies'][packageName];
|
442 | });
|
443 | return writeFile('package.json', JSON.stringify(packageJson, null, 2));
|
444 | })
|
445 | .then(() => JSON.parse(ts.sys.readFile(tsConfigPath)))
|
446 | .then((tsConfigJson) => {
|
447 | if (!tsConfigJson.exclude || force) {
|
448 |
|
449 | tsConfigJson.exclude = [
|
450 | 'test.ts',
|
451 | '**/*.spec.ts'
|
452 | ];
|
453 | }
|
454 | return writeFile(tsConfigPath, JSON.stringify(tsConfigJson, null, 2));
|
455 | })
|
456 | .then(() => writeFile('webpack.config.js', webpackConfigStr))
|
457 | .then(() => {
|
458 |
|
459 | config.project.ejected = true;
|
460 | cliConfig.save();
|
461 | })
|
462 | .then(() => {
|
463 | console.log(chalk_1.yellow(common_tags_1.stripIndent `
|
464 | ==========================================================================================
|
465 | Ejection was successful.
|
466 |
|
467 | To run your builds, you now need to do the following commands:
|
468 | - "npm run build" to build.
|
469 | - "npm run test" to run unit tests.
|
470 | - "npm start" to serve the app using webpack-dev-server.
|
471 | - "npm run e2e" to run protractor.
|
472 |
|
473 | Running the equivalent CLI commands will result in an error.
|
474 |
|
475 | ==========================================================================================
|
476 | Some packages were added. Please run "npm install".
|
477 | `));
|
478 | });
|
479 | }
|
480 | });
|
481 |
|
\ | No newline at end of file |