1 | const path = require('path');
|
2 | const _ = require('lodash');
|
3 | const hash = require('hash-sum');
|
4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
5 | const {getBabelConfig} = require('./babel');
|
6 |
|
7 | function 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;
|
26 | });
|
27 |
|
28 | return {
|
29 | cacheDirectory,
|
30 | cacheIdentifier: hash({
|
31 | versions,
|
32 | vars: options.vars,
|
33 | }),
|
34 | };
|
35 | }
|
36 |
|
37 | function 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 |
|
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 |
|
89 | function 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 |
|
122 | function getJsonLoader() {
|
123 | return {
|
124 | test: /\.json$/,
|
125 | loader: 'json-loader',
|
126 | };
|
127 | }
|
128 |
|
129 | function 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 |
|
139 | formatter: require('eslint-friendly-formatter'),
|
140 | },
|
141 | };
|
142 | }
|
143 |
|
144 | function getFileHash(config, options) {
|
145 | if (!config.appendHash || options.appendHash === false) {
|
146 | return '';
|
147 | }
|
148 |
|
149 |
|
150 | return '.[hash:base62:5]';
|
151 | }
|
152 |
|
153 | function 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 |
|
169 | function 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 |
|
181 | function getImageLoader(config) {
|
182 | return getUrlFileLoader(config, {
|
183 | test: /\.(png|jpe?g|webp|gif|ico)(\?.*)?$/,
|
184 | outputPath: 'img/',
|
185 | });
|
186 | }
|
187 |
|
188 | function getSvgLoader(config) {
|
189 |
|
190 |
|
191 | return getFileLoader(config, {
|
192 | test: /\.(svg)(\?.*)?$/,
|
193 | outputPath: 'img/',
|
194 | });
|
195 | }
|
196 |
|
197 | function getFaviconIcoLoader(config) {
|
198 |
|
199 |
|
200 | return getFileLoader(config, {
|
201 | test: /\favicon\.ico(\?.*)?$/,
|
202 | outputPath: '/',
|
203 | appendHash: false,
|
204 | });
|
205 | }
|
206 |
|
207 | function getFontLoader(config) {
|
208 | return getUrlFileLoader(config, {
|
209 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
210 | outputPath: 'fonts/',
|
211 | });
|
212 | }
|
213 |
|
214 | function getMediaLoader(config) {
|
215 | return getUrlFileLoader(config, {
|
216 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac|av1)(\?.*)?$/,
|
217 | outputPath: 'media/',
|
218 | });
|
219 | }
|
220 |
|
221 | function getStyleLoader(loaders, options = {}, config) {
|
222 | const cssLoader = ['css', {importLoaders: 2}];
|
223 |
|
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 |
|
271 | function getSSRStyleLoaders() {
|
272 | return [{
|
273 | test: /\.(styl(us)?|(post|p|s)?css|less|sass)$/,
|
274 | loader: 'null-loader',
|
275 | }];
|
276 | }
|
277 |
|
278 | function 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 |
|
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 |
|
308 | function 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 |
|
328 | module.exports = {
|
329 | getLoaders,
|
330 | };
|