UNPKG

8.42 kBJavaScriptView Raw
1/* eslint-disable global-require */
2const _ = require('lodash');
3const fs = require('fs');
4const path = require('path');
5const chalk = require('chalk');
6const hash = require('hash-sum');
7const notifier = require('node-notifier');
8const webpack = require('webpack');
9const CleanWebpackPlugin = require('clean-webpack-plugin');
10const FriendlyErrors = require('friendly-errors-webpack-plugin');
11const MiniCssExtractPlugin = require('mini-css-extract-plugin');
12const CopyWebpackPlugin = require('copy-webpack-plugin');
13const {GhostProgressPlugin} = require('ghost-progress-webpack-plugin');
14const {VueLoaderPlugin} = require('vue-loader');
15
16const getDevServerUrls = require('../util/getDevServerUrls');
17
18/**
19 * Generate dist index.html with correct asset hash for caching.
20 * you can customize output by editing /index.html
21 * @see https://github.com/ampedandwired/html-webpack-plugin
22 */
23function getHtmlWebpackPlugin(config = {}) {
24 // eslint-disable-next-line
25 const HtmlWebpackPlugin = require('html-webpack-plugin');
26
27 let minify = false;
28 if (config.minify) {
29 minify = {
30 removeComments: true,
31 collapseWhitespace: true,
32 collapseBooleanAttributes: true,
33 removeScriptTypeAttributes: true,
34 collapseInlineTagWhitespace: true,
35 minifyCSS: true,
36 minifyJS: true,
37 // more options:
38 // https://github.com/kangax/html-minifier#options-quick-reference
39 };
40 }
41
42 return new HtmlWebpackPlugin({
43 filename: `${config.entryHtml}`,
44 template: path.join(config.sourcePath, config.entryHtml),
45 inject: true,
46 minify,
47 // necessary to consistently work with multiple chunks via CommonsChunkPlugin
48 chunksSortMode: 'dependency',
49 });
50}
51
52function getCompressionPlugin() {
53 const CompressionWebpackPlugin = require('compression-webpack-plugin');
54
55 const ext = ['js', 'css', 'xml', 'json', 'ttf', 'svg'];
56 const regex = new RegExp(`\\.(?:${ext.join('|')})$`);
57 return new CompressionWebpackPlugin({
58 asset: '[path].gz[query]',
59 algorithm: 'gzip',
60 test: regex,
61 threshold: 4096,
62 minRatio: 0.8,
63 cache: true,
64 });
65}
66
67/**
68 * generate bundle size stats so we can analyze them
69 * to see which dependecies are the heaviest
70 */
71function getBundleAnalyzerPlugin(config) {
72 const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer');
73
74 const options = {
75 analyzerMode: 'static',
76 reportFilename: 'webpack.report.html',
77 generateStatsFile: false,
78 statsFilename: 'webpack.stats.json',
79 openAnalyzer: false,
80 };
81
82 if (_.isPlainObject(config.analyzeBundle)) {
83 Object.assign(options, config.analyzeBundle);
84 }
85
86 return new BundleAnalyzerPlugin(options);
87}
88
89function getDevelopmentPlugins(config) {
90 const plugins = [];
91 if (!config.hasHotClient && !config.isSSR) {
92 plugins.push(
93 // Hot Module Replacement
94 new webpack.HotModuleReplacementPlugin(),
95 );
96 }
97
98 return plugins;
99}
100
101/**
102 * Keep chunk ids stable so async chunks have consistent hash (#1916)
103 * @see https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/cli-service/lib/config/app.js
104 */
105function getNamedChunksPlugin() {
106 return new webpack.NamedChunksPlugin((chunk) => {
107 if (chunk.name) {
108 return chunk.name;
109 }
110
111 const joinedHash = hash(
112 Array.from(chunk.modulesIterable, m => m.id).join('_')
113 );
114 return 'chunk-' + joinedHash;
115 });
116}
117
118function getCSSFileName(config) {
119 if (!config.appendHash) return 'css/[name].js';
120 // webpack does not support base62 encoding in contenthash yet
121 if (config.isProduction) return 'css/[name].[contenthash:7].css';
122 return 'css/[name].js';
123}
124
125function getProductionPlugins(config) {
126 const plugins = [
127 // extract css into its own file
128 // MiniCssExtractPlugin does not support base62 hash yet
129 new MiniCssExtractPlugin({
130 filename: getCSSFileName(config),
131 chunkFilename: getCSSFileName(config),
132 }),
133
134 // keep module.id stable when vendor modules does not change
135 new webpack.HashedModuleIdsPlugin(),
136 ];
137
138 if (!config.isSSR) {
139 plugins.push(
140 // clean the dist folder
141 new CleanWebpackPlugin({
142 // perform clean just before files are emitted to the output dir
143 cleanAfterEveryBuildPatterns: [
144 // don't remove the ssr server bundle
145 '!vue-ssr-server-bundle.json',
146 '!server-bundle.json',
147 '!server-bundle.js',
148 '!vue-ssr-client-manifest.json',
149 '!client-manifest.json',
150 ],
151 verbose: false,
152 }),
153
154 // remove all momentjs locale except for the en-gb locale
155 // this helps in reducing momentjs size by quite a bit
156 new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /en-gb/),
157 );
158 }
159
160 return plugins;
161}
162
163function getDevServerMessages(config) {
164 if (!config.isDevServer) return undefined;
165
166 const urls = getDevServerUrls(config);
167 return {
168 messages: [
169 `${chalk.bold('Local')}: ${urls.local}`,
170 `${chalk.bold('Network')}: ${urls.network}`,
171 ],
172 };
173}
174
175function getFriendlyErrorsPlugin(config) {
176 if (config.isSSRDevServer) return null;
177
178 let onErrors;
179 if (config.isDevServer && config.devServer.notifyOnError) {
180 onErrors = (severity, errors) => {
181 if (severity !== 'error') {
182 return;
183 }
184
185 const error = errors[0];
186 notifier.notify({
187 title: 'Webpack Error',
188 message: `${error.name}\nin ${error.file || ''}`,
189 subtitle: error.file || '',
190 icon: null,
191 });
192 };
193 }
194
195 // friendly error plugin displays very confusing errors when webpack
196 // fails to resolve a loader, so we provide custom handlers to improve it
197 const {transformer, formatter} = require('../util/resolveLoaderError'); // eslint-disable-line
198 return new FriendlyErrors({
199 compilationSuccessInfo: getDevServerMessages(config),
200 onErrors,
201 additionalTransformers: [transformer],
202 additionalFormatters: [formatter],
203 });
204}
205
206function getCopyPlugin(config) {
207 const from = path.join(config.sourcePath, 'public');
208 // don't use plugin if source folder does not exist
209 if (!fs.existsSync(from)) {
210 return null;
211 }
212
213 return new CopyWebpackPlugin([
214 {
215 from,
216 to: path.join(config.destPath, 'public'),
217 toType: 'dir',
218 },
219 ]);
220}
221
222function getProgressPlugin() {
223 if (process.stdout.isTTY) {
224 return new GhostProgressPlugin();
225 }
226
227 // simple progress for non-tty environments
228 const SimpleProgressPlugin = require('../util/simpleProgress');
229 return SimpleProgressPlugin();
230}
231
232function getSSRPlugins(config) {
233 const VueSSRServerPlugin = require('vue-server-renderer/server-plugin');
234 const env = config.isProduction ? 'production' : 'development';
235 return [
236 new webpack.DefinePlugin({
237 'process.env.NODE_ENV': JSON.stringify(env),
238 'process.env.VUE_ENV': JSON.stringify('server'),
239 window: 'undefined',
240 }),
241
242 // This is the plugin that turns the entire output of the server build
243 // into a single JSON file.
244 new VueSSRServerPlugin({
245 filename: 'vue-ssr-server-bundle.json',
246 }),
247 ];
248}
249
250function getPlugins(config = {}) {
251 const plugins = [];
252
253 plugins.push(
254 // vue loader plugin is required for vue files
255 new VueLoaderPlugin(),
256
257 // named chunks for consistant hasing of async chunks
258 getNamedChunksPlugin(config),
259
260 // better errors
261 getFriendlyErrorsPlugin(config),
262
263 // progress
264 getProgressPlugin(config),
265 );
266
267 // these plugins are not needed for SSR
268 if (!config.isSSR) {
269 // copy public assets if not dev server
270 if (!config.isDevServer) {
271 plugins.push(getCopyPlugin(config));
272 }
273
274 if (!config.library && config.entryHtml) {
275 plugins.push(getHtmlWebpackPlugin(config));
276 }
277
278 if (config.gzip) {
279 plugins.push(getCompressionPlugin(config));
280 }
281
282 if (config.analyzeBundle) {
283 plugins.push(getBundleAnalyzerPlugin(config));
284 }
285 }
286
287 if (config.isSSR) {
288 plugins.push(...getSSRPlugins(config));
289 }
290
291 // if we are given ssr config, we would want to inject the plugin into client bundle
292 if (config.hasSSR && !config.isSSR) {
293 // This plugins generates `vue-ssr-client-manifest.json` in the
294 // output directory.
295 // With the client manifest and the server bundle,
296 // the renderer now has information of both the server and client builds,
297 // so it can automatically infer and inject
298 // preload / prefetch directives and css links / script tags
299 // into the rendered HTML.
300 const VueSSRClientPlugin = require('vue-server-renderer/client-plugin');
301 plugins.push(new VueSSRClientPlugin({
302 filename: 'vue-ssr-client-manifest.json',
303 }));
304 }
305
306 if (config.isProduction) {
307 plugins.push(...getProductionPlugins(config));
308 }
309 else {
310 plugins.push(...getDevelopmentPlugins(config));
311 }
312
313 return plugins.filter(Boolean);
314}
315
316module.exports = {
317 getPlugins,
318};