UNPKG

6.97 kBJavaScriptView Raw
1const path = require('path');
2const _ = require('lodash');
3const hash = require('hash-sum');
4const MiniCssExtractPlugin = require('mini-css-extract-plugin');
5const {getBabelConfig} = require('./babel');
6
7function getCacheConfig(config, id, options = {}) {
8 let cacheDirectory = path.join(
9 config.cwd,
10 'node_modules',
11 '.cache',
12 'sm-webpack',
13 id
14 );
15
16 if (config.isSSR) {
17 cacheDirectory = path.join(cacheDirectory, 'ssr');
18 }
19
20 const modules = options.modules || [];
21 modules.push('cache-loader');
22
23 const versions = {};
24 modules.forEach((mod) => {
25 versions[mod] = require(`${mod}/package.json`).version; // eslint-disable-line
26 });
27
28 return {
29 cacheDirectory,
30 cacheIdentifier: hash({
31 versions,
32 vars: options.vars,
33 }),
34 };
35}
36
37function getJsLoader(config) {
38 const babelConfig = getBabelConfig(config);
39 const {debug, includeModules} = babelConfig.options;
40 delete babelConfig.options;
41 const cacheConfig = getCacheConfig(config, 'babel-loader', {
42 modules: [
43 '@babel/core',
44 '@babel/preset-env',
45 'babel-loader',
46 ],
47 vars: {babelConfig},
48 });
49
50 const cacheLoader = {
51 loader: 'cache-loader',
52 options: cacheConfig,
53 };
54
55 const babelLoader = {
56 loader: 'babel-loader',
57 options: babelConfig,
58 };
59
60 let modulesRegex;
61 if (_.isRegExp(includeModules)) {
62 modulesRegex = includeModules;
63 }
64 else if (includeModules.length) {
65 modulesRegex = new RegExp(`(${_.map(includeModules, _.escapeRegExp).join('|')})`);
66 }
67
68 // TODO: maybe use thread-loader here
69
70 return {
71 test: /\.m?jsx?$/,
72 exclude: (filePath) => {
73 if (filePath.includes('.vue')) return false;
74 if (filePath.includes('.jsx')) return false;
75 if (filePath.includes('.mjs')) return false;
76 if (modulesRegex && modulesRegex.test(filePath)) {
77 return false;
78 }
79 if (filePath.includes('node_modules')) return true;
80 return false;
81 },
82 use: debug ? babelLoader : [
83 cacheLoader,
84 babelLoader,
85 ],
86 };
87}
88
89function getVueLoader(config) {
90 const cacheConfig = getCacheConfig(config, 'vue-loader', {
91 modules: [
92 'vue-loader',
93 '@vue/component-compiler-utils',
94 'vue-template-compiler',
95 ],
96 });
97
98 const cacheLoader = {
99 loader: 'cache-loader',
100 options: cacheConfig,
101 };
102
103 const vueLoader = {
104 loader: 'vue-loader',
105 options: {
106 compilerOptions: {
107 preserveWhitespace: false,
108 },
109 ...cacheConfig,
110 },
111 };
112
113 return {
114 test: /\.vue$/,
115 use: [
116 cacheLoader,
117 vueLoader,
118 ],
119 };
120}
121
122function getJsonLoader() {
123 return {
124 test: /\.json$/,
125 loader: 'json-loader',
126 };
127}
128
129function getEslintLoader(config) {
130 return {
131 test: /\.(vue|m?jsx?)$/,
132 loader: 'eslint-loader',
133 include: config.sourcePath,
134 exclude: /node_modules/,
135 enforce: 'pre',
136 options: {
137 cache: true,
138 // eslint-disable-next-line
139 formatter: require('eslint-friendly-formatter'),
140 },
141 };
142}
143
144function getFileHash(config, options) {
145 if (!config.appendHash || options.appendHash === false) {
146 return '';
147 }
148
149 // hash is basically contenthash here, file-loader does not have contenthash
150 return '.[hash:base62:5]';
151}
152
153function getUrlFileLoader(config, options = {}) {
154 return {
155 test: options.test,
156 use: [{
157 loader: 'url-loader',
158 options: {
159 limit: 3000,
160 fallback: 'file-loader',
161 outputPath: options.outputPath,
162 name: `[name]${getFileHash(config, options)}.[ext]`,
163 emitFile: !config.isSSR,
164 },
165 }],
166 };
167}
168
169function getFileLoader(config, options = {}) {
170 return {
171 test: options.test,
172 loader: 'file-loader',
173 options: {
174 outputPath: options.outputPath,
175 name: `[name]${getFileHash(config, options)}.[ext]`,
176 emitFile: !config.isSSR,
177 },
178 };
179}
180
181function getImageLoader(config) {
182 return getUrlFileLoader(config, {
183 test: /\.(png|jpe?g|webp|gif|ico)(\?.*)?$/,
184 outputPath: 'img/',
185 });
186}
187
188function getSvgLoader(config) {
189 // do not base64-inline SVGs.
190 // https://github.com/facebookincubator/create-react-app/pull/1180
191 return getFileLoader(config, {
192 test: /\.(svg)(\?.*)?$/,
193 outputPath: 'img/',
194 });
195}
196
197function getFaviconIcoLoader(config) {
198 // do not base64-inline SVGs.
199 // https://github.com/facebookincubator/create-react-app/pull/1180
200 return getFileLoader(config, {
201 test: /\favicon\.ico(\?.*)?$/,
202 outputPath: '/',
203 appendHash: false,
204 });
205}
206
207function getFontLoader(config) {
208 return getUrlFileLoader(config, {
209 test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
210 outputPath: 'fonts/',
211 });
212}
213
214function getMediaLoader(config) {
215 return getUrlFileLoader(config, {
216 test: /\.(mp4|webm|ogg|mp3|wav|flac|aac|av1)(\?.*)?$/,
217 outputPath: 'media/',
218 });
219}
220
221function getStyleLoader(loaders, options = {}, config) {
222 const cssLoader = ['css', {importLoaders: 2}];
223 // enable module support
224 if (options.modules) {
225 cssLoader[1].modules = true;
226 let localIdentName = '[hash:base62:6]';
227 if (!config.isProduction) {
228 localIdentName = `[name]_[local]_${localIdentName}`;
229 }
230 cssLoader[1].localIdentName = localIdentName;
231 }
232
233 loaders = [cssLoader].concat(loaders);
234 const sourceLoaders = loaders.map((loader) => {
235 let loaderName = '';
236 let loaderOptions = {};
237
238 if (Array.isArray(loader)) {
239 loaderName = loader[0] + '-loader';
240 loaderOptions = loader[1];
241 }
242 else {
243 loaderName = loader + '-loader';
244 }
245
246 const loaderObj = {
247 loader: loaderName,
248 options: loaderOptions,
249 };
250
251 if (config.sourceMap) {
252 loaderObj.options.sourceMap = true;
253 }
254
255 return loaderObj;
256 });
257
258 if (config.isProduction) {
259 return [MiniCssExtractPlugin.loader].concat(sourceLoaders);
260 }
261
262 return [{
263 loader: 'vue-style-loader',
264 options: {
265 hmr: config.isProduction,
266 sourceMap: Boolean(config.sourceMap),
267 },
268 }].concat(sourceLoaders);
269}
270
271function getSSRStyleLoaders() {
272 return [{
273 test: /\.(styl(us)?|(post|p|s)?css|less|sass)$/,
274 loader: 'null-loader',
275 }];
276}
277
278function getStyleLoaders(config) {
279 if (config.isSSR) {
280 return getSSRStyleLoaders();
281 }
282
283 const loaders = {
284 'css|postcss|pcss': [['postcss', {
285 config: {path: __dirname},
286 }]],
287 less: ['less'],
288 sass: [['sass', {indentedSyntax: true}]],
289 scss: ['sass'],
290 'stylus|styl': ['stylus'],
291 };
292
293 return Object.keys(loaders).map((extension) => {
294 const loader = loaders[extension];
295 return {
296 test: new RegExp(`\\.(${extension})$`),
297 oneOf: [
298 // enable support of css modules conditionally
299 {resourceQuery: /module/, use: getStyleLoader(loader, {modules: true}, config)},
300 {resourceQuery: /\?vue/, use: getStyleLoader(loader, {}, config)},
301 {test: /\.module\.\w+$/, use: getStyleLoader(loader, {modules: true}, config)},
302 {use: getStyleLoader(loader, {modules: config.cssModules}, config)},
303 ],
304 };
305 });
306}
307
308function getLoaders(config) {
309 const loaders = [
310 getVueLoader(config),
311 getJsLoader(config),
312 getJsonLoader(config),
313 getImageLoader(config),
314 getSvgLoader(config),
315 getFaviconIcoLoader(config),
316 getFontLoader(config),
317 getMediaLoader(config),
318 ...getStyleLoaders(config),
319 ];
320
321 if (config.eslint) {
322 loaders.push(getEslintLoader(config));
323 }
324
325 return loaders;
326}
327
328module.exports = {
329 getLoaders,
330};