2021-02-08 19:21:30 +08:00

312 lines
8.7 KiB
JavaScript

import { join } from 'path';
import { existsSync } from 'fs';
import Config from 'webpack-chain';
import webpack from 'webpack';
import createCssWebpackConfig from './css';
import getBableOpts from './getBableOpts';
import createVueWebpackConfig from './vue';
import createDefineWebpackConfig from './define';
import createMinimizerWebpackConfig from './minimizer';
import createHtmlWebpackConfig from './html';
function getTargetsAndBrowsersList({ config }) {
let targets = config.targets || {};
targets = Object.keys(targets)
.filter(key => targets[key] !== false)
.reduce((memo, key) => {
memo[key] = targets[key];
return memo;
}, {});
const browserslist = targets.browsers
|| Object.keys(targets).map(key => `${key} >= ${targets[key] === true ? '0' : targets[key]}`);
return {
targets,
browserslist
};
}
const DEFAULT_EXCLUDE_NODE_MODULES = [
'vue',
'vuex',
'vue-router',
'ant-design-vue',
'core-js',
'echarts',
'@babel/runtime',
'lodash',
'webpack-dev-server',
'ansi-html',
'html-entities'
];
function genTranspileDepRegex(exclude) {
exclude = exclude.concat(DEFAULT_EXCLUDE_NODE_MODULES);
const deps = exclude.map((dep) => {
if (typeof dep === 'string') {
const depPath = join('node_modules', dep, '/');
return /^win/.test(require('os').platform()) ? depPath.replace(/\\/g, '\\\\') : depPath;
} if (dep instanceof RegExp) {
return dep.source;
}
throw new Error('exclude only accepts an array of string or regular expressions');
});
return deps.length ? new RegExp(deps.join('|')) : null;
}
export default async function getConfig({
cwd,
config,
env,
entry = {},
modifyBabelOpts,
modifyBabelPresetOpts,
chainWebpack,
headScripts
}) {
const isDev = env === 'development';
const isProd = env === 'production';
const webpackConfig = new Config();
const absoluteOutput = join(cwd, config.outputPath || 'dist');
webpackConfig.mode(env);
webpackConfig.stats('verbose');
webpackConfig.externals(config.externals || {});
webpackConfig.devtool(isDev ? (config.devtool || 'cheap-module-source-map') : config.devtool);
// --------------- cache -----------
webpackConfig.cache({
type: 'filesystem',
cacheDirectory: join(cwd, '.cache/webpack')
});
// --------------- entry -----------
// Feature 公共模块 vue vue-router 处理 dependOn ?
Object.keys(entry).forEach((key) => {
webpackConfig.entry(key).add(entry[key]).end();
});
// --------------- output -----------
webpackConfig.output
.path(absoluteOutput)
.publicPath(config.publicPath || '')
.filename('[name].[contenthash:8].js')
.chunkFilename('[name].[contenthash:8].chunk.js');
// --------------- resolve -----------
webpackConfig.resolve.extensions.merge(['.mjs', '.js', '.jsx', '.vue', '.json', '.wasm']);
if (config.alias) {
Object.keys(config.alias).forEach((key) => {
webpackConfig.resolve.alias
.set(key, config.alias[key]);
});
}
// --------------- module -----------
webpackConfig.module
.rule('image')
.test(/\.(png|jpe?g|gif|webp|ico)(\?.*)?$/)
.use('url-loader')
.loader(require.resolve('url-loader'))
.options({
limit: config.inlineLimit || 8192,
esModule: false,
fallback: {
loader: require.resolve('file-loader'),
options: {
name: 'static/[name].[hash:8].[ext]',
esModule: false
}
}
});
webpackConfig.module
.rule('svg')
.test(/\.(svg)(\?.*)?$/)
.use('file-loader')
.loader(require.resolve('file-loader'))
.options({
name: 'static/[name].[hash:8].[ext]',
esModule: false
});
webpackConfig.module
.rule('fonts')
.test(/\.(eot|woff|woff2|ttf)(\?.*)?$/)
.use('file-loader')
.loader(require.resolve('file-loader'))
.options({
name: 'static/[name].[hash:8].[ext]',
esModule: false
});
webpackConfig.module
.rule('raw')
.test(/\.(txt|text|md)$/)
.use('raw-loader')
.loader(require.resolve('raw-loader'))
.options({
esModule: false
});
const { targets, browserslist } = getTargetsAndBrowsersList({ config });
const babelOpts = await getBableOpts({
cwd,
modifyBabelOpts,
modifyBabelPresetOpts,
targets
});
// --------------- js -----------
webpackConfig.module
.rule('js')
.test(/\.(js|mjs|jsx)$/)
.exclude.add((filepath) => {
// always transpile js in vue files
if (/\.vue\.jsx?$/.test(filepath)) {
return false;
}
// Don't transpile node_modules
return /node_modules/.test(filepath);
}).end()
.use('babel-loader')
.loader(require.resolve('babel-loader'))
.options(babelOpts);
// 为了避免第三方依赖包编译不充分导致线上问题,默认对 node_modules 也进行全编译
const transpileDepRegex = genTranspileDepRegex(config.nodeModulesTransform.exclude);
webpackConfig.module
.rule('js-in-node_modules')
.test(/\.(js|mjs)$/)
.include.add(/node_modules/).end()
.exclude.add((filepath) => {
if (transpileDepRegex && transpileDepRegex.test(filepath)) {
return true;
}
return false;
}).end()
.use('babel-loader')
.loader(require.resolve('babel-loader'))
.options(babelOpts);
// --------------- css -----------
createCssWebpackConfig({
isDev,
config,
webpackConfig,
browserslist
});
// --------------- vue -----------
createVueWebpackConfig({
config,
webpackConfig
});
// --------------- html -----------
const { publicCopyIgnore } = await createHtmlWebpackConfig({
cwd,
config,
webpackConfig,
headScripts,
isProd
});
// --------------- copy -----------
const copyPatterns = [existsSync(join(cwd, 'public')) && {
from: join(cwd, 'public'),
filter: (resourcePath) => {
if (resourcePath.indexOf('.DS_Store') !== -1) {
return false;
}
if (publicCopyIgnore.includes(resourcePath)) {
return false;
}
return true;
},
to: absoluteOutput
}, ...((config.copy || []).map((item) => {
if (typeof item === 'string') {
return {
from: join(cwd, item.from),
to: absoluteOutput
};
}
return {
from: join(cwd, item.from),
to: join(absoluteOutput, item.to)
};
}))].filter(Boolean);
// const publicCopyIgnore = ['.DS_Store'];
webpackConfig
.plugin('copy')
.use(require.resolve('copy-webpack-plugin'), [{
patterns: copyPatterns
}]);
// --------------- define -----------
createDefineWebpackConfig({
config,
webpackConfig
});
// --------------- 分包 -----------
if (isProd) {
webpackConfig.optimization.splitChunks({
cacheGroups: {
defaultVendors: {
name: 'chunk-vendors',
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: 'chunk-common',
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
}
});
}
// --------------- 压缩 -----------
createMinimizerWebpackConfig({
isProd,
config,
webpackConfig
});
// --------------- 构建输出 ----------
webpackConfig
.plugin('progress')
.use(require.resolve('webpackbar'));
webpackConfig
.plugin('friendly-errors')
.use(require('@soda/friendly-errors-webpack-plugin'));
// --------------- chainwebpack -----------
if (chainWebpack) {
await chainWebpack(webpackConfig, {
webpack
});
}
// 用户配置的 chainWebpack 优先级最高
if (config.chainWebpack) {
await config.chainWebpack(webpackConfig, {
env,
webpack
});
}
return webpackConfig.toConfig();
}