1 | const path = require('path')
|
2 | const fs = require('fs')
|
3 |
|
4 | const webpack = require('webpack')
|
5 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
|
6 | const cssnano = require('cssnano')
|
7 |
|
8 | const { clientConfig, serverConfig, cordovaConfig } = require('./webpack.config')
|
9 | const { ExtractTextLoader } = require('./common')
|
10 |
|
11 | // get the last argument, see the dev.js
|
12 | const argv = process.argv[2]
|
13 |
|
14 | const commonPlugins = [
|
15 | new webpack.DefinePlugin({
|
16 | 'process.env.TEST': false,
|
17 | 'process.env.NODE_ENV': '"production"',
|
18 | }),
|
19 | new ExtractTextPlugin({
|
20 | filename: 'styles_[contenthash:7].css',
|
21 | allChunks: false, // default, so when using System.import(), css will be inlined into <style />
|
22 | }),
|
23 | new webpack.optimize.AggressiveMergingPlugin(),
|
24 | new webpack.optimize.UglifyJsPlugin({
|
25 | compress: {
|
26 | warnings: false,
|
27 | },
|
28 | sourceMap: false,
|
29 | comments: false,
|
30 | 'screw-ie8': true,
|
31 | }),
|
32 | ]
|
33 |
|
34 |
|
35 | /**
|
36 | * Client
|
37 | */
|
38 |
|
39 | clientConfig.module.rules.push(...ExtractTextLoader)
|
40 | clientConfig.plugins.push(...commonPlugins)
|
41 | const commonsChunk = new webpack.optimize.CommonsChunkPlugin({
|
42 | name: 'vendor',
|
43 | minChunks: ({ resource }) => /node_modules/.test(resource), // could use /node_modules.*\.jsx?$/ to only process js
|
44 | })
|
45 | clientConfig.plugins.push(new webpack.HashedModuleIdsPlugin(), commonsChunk)
|
46 | // clientConfig.plugins.push(commonsChunk, new require('webpack-chunk-hash'))
|
47 |
|
48 | /*
|
49 | There are 3+ ways to dynamically create vendor chunk for long term caching using CommonsChunkPlugin
|
50 | After creating a commonChunk
|
51 | new webpack.optimize.CommonsChunkPlugin({
|
52 | name: 'vendor',
|
53 | minChunks: ({ resource }) => /node_modules/.test(resource),
|
54 | })
|
55 |
|
56 | 1) Add new require('webpack-md5-hash') or require('webpack-chunk-hash')
|
57 | The hash will be based on content, the manifest webpackJsonp function will be in the entry chunk, in this case the vendor chunk
|
58 | There will output 2 files: vendor.xx.js and client.xx.js
|
59 | vendor.xx.js is the entry chunk and will be loaded first
|
60 | 2) Add another commonChunk
|
61 | new webpack.optimize.CommonsChunkPlugin('manifest') <-- can be any name, such as 'meta'
|
62 | this will create a manifest.[xxx].js or meta.[xxx].js which contains the webpackJsonp functions
|
63 | There will create 3 files: manifest.xx.js, vendor.xx.js and client.xx.js
|
64 | When the client code changes, the xx in client.xx.js and the xx in manifest.xx.js will change
|
65 | the xx in vendor.xx.js will be the same
|
66 | no need extra plugin, but will results in 3 http requests
|
67 | 3) Add new require('chunk-manifest-webpack-plugin') after adding new webpack.optimize.CommonsChunkPlugin('manifest')
|
68 | This plugin extract the manifest into a json file
|
69 | Then will need inline-manifest-webpack-plugin to insert the json into the intial html along with html-webpack-plugin
|
70 | This will result in 2 files: vendor.xx.js and client.xx.js
|
71 |
|
72 | ** I have decided to go for option 1 as it seems simplest, clients only need to make 2 http request, but need to be dependent on the md5 plugins
|
73 | */
|
74 |
|
75 |
|
76 | if (argv !== 'cordovaOnly') {
|
77 | webpack(clientConfig).run((err, stats) => {
|
78 | if (err) throw err
|
79 | console.log('Client Bundles \n', stats.toString({
|
80 | colors: true,
|
81 | }), '\n')
|
82 | // cssnano, temparory work around
|
83 | const assets = require(path.resolve('build/webpack-assets.json')) // eslint-disable-line import/no-dynamic-require
|
84 | for (const entry in assets) { // eslint-disable-line
|
85 | const filename = assets[entry].css
|
86 | if (filename) {
|
87 | const filePath = path.resolve('build/public', filename.replace(/^\/+/, ''))
|
88 | fs.readFile(filePath, (e, css) => {
|
89 | cssnano.process(css, { discardComments: { removeAll: true } })
|
90 | .then(result => {
|
91 | fs.writeFile(filePath, result.css, () => {})
|
92 | })
|
93 | })
|
94 | }
|
95 | }
|
96 | })
|
97 | }
|
98 |
|
99 | /**
|
100 | * Server
|
101 | */
|
102 |
|
103 | serverConfig.module.rules.push(ExtractTextLoader[1]) // handle the css module
|
104 | serverConfig.plugins.push(...commonPlugins)
|
105 | // remove the ExtractTextPlugin
|
106 | serverConfig.plugins = serverConfig.plugins.filter(p => !(p instanceof ExtractTextPlugin))
|
107 | // re-add the ExtractTextPlugin with new option
|
108 | serverConfig.plugins.push(new ExtractTextPlugin({ filename: 'styles.css', allChunks: true })) // set allChunks to true to move all css into styles.css which will be deleted in the following build step
|
109 |
|
110 | if (argv !== 'cordovaOnly') {
|
111 | webpack(serverConfig).run((err, stats) => {
|
112 | if (err) throw err
|
113 | console.log('Server Bundle \n', stats.toString({
|
114 | colors: true,
|
115 | }), '\n')
|
116 | require('child_process').exec('rm build/server/styles.css', () => {}) // delele the styles.css in the server folder
|
117 | // try {
|
118 | // const styleFile = _root + '/build/server/styles.css'
|
119 | // fs.statSync(styleFile) && fs.unlinkSync(styleFile)
|
120 | // } catch(e) {/*do nothing*/}
|
121 | // file loader may also result in duplicated files from shared React components
|
122 | })
|
123 | }
|
124 |
|
125 | /**
|
126 | * Cordova
|
127 | */
|
128 |
|
129 | cordovaConfig.module.rules.push(...ExtractTextLoader)
|
130 | cordovaConfig.plugins.push(...commonPlugins)
|
131 |
|
132 | // remove the ExtractTextPlugin
|
133 | cordovaConfig.plugins = cordovaConfig.plugins.filter(p => !(p instanceof ExtractTextPlugin))
|
134 | // re-add the ExtractTextPlugin with new option
|
135 | cordovaConfig.plugins.push(new ExtractTextPlugin({ filename: 'styles.css', allChunks: true }))
|
136 |
|
137 | if (argv === 'all' || argv === 'cordovaOnly') {
|
138 | webpack(cordovaConfig).run((err, stats) => { // eslint-disable-line no-unused-expressions
|
139 | if (err) throw err
|
140 | console.log('Cordova Bundles \n', stats.toString({
|
141 | colors: true,
|
142 | }), '\n')
|
143 |
|
144 | const filePath = path.resolve(cordovaConfig.output.path, 'styles.css')
|
145 | fs.readFile(filePath, (e, css) => {
|
146 | cssnano.process(css, { discardComments: { removeAll: true } })
|
147 | .then(result => {
|
148 | fs.writeFile(filePath, result.css, () => {})
|
149 | })
|
150 | })
|
151 | })
|
152 | }
|