fes.js/packages/fes/build/configs/webpack.config.js
2020-11-02 09:51:40 +08:00

487 lines
17 KiB
JavaScript

const path = require('path');
const fs = require('fs');
const merge = require('webpack-merge');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { VueLoaderPlugin } = require('vue-loader');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const FriendlyErrorsPlugin = require('@soda/friendly-errors-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const OptimizeCssnanoPlugin = require('@intervolga/optimize-cssnano-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlPlugin = require('html-webpack-plugin');
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const autoprefixer = require('autoprefixer');
const browsers = require('../helpers/browser');
function handleGzipCompress(compress) {
if (!compress) return false;
if (typeof compress === 'boolean') {
return {};
}
return compress;
}
module.exports = function webpackConfig(configs, webpack, mode) {
let template = path.resolve(
configs.folders.PROJECT_DIR,
'./publish/index.html'
);
if (!fs.existsSync(template)) {
template = path.resolve(configs.folders.FES_DIR, './src/index.html');
}
const isDev = mode === 'dev';
const isBuild = mode === 'build';
const gzipCompress = handleGzipCompress(configs.compress);
const presets = [
[
require.resolve('@babel/preset-env')
]
];
const plugins = [
[require.resolve('@vue/babel-plugin-jsx')],
[
require.resolve('@babel/plugin-transform-runtime'), {
corejs: 3
}
],
require.resolve('@babel/plugin-proposal-object-rest-spread'),
require.resolve('@babel/plugin-syntax-dynamic-import')
];
const cssloaders = [
isDev
? {
loader: require.resolve('vue-style-loader'),
options: {
sourceMap: false,
shadowMode: false
}
}
: {
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../'
}
},
{
loader: require.resolve('css-loader'),
options: {
sourceMap: false,
importLoaders: 2
}
},
{
loader: require.resolve('postcss-loader'),
options: {
postcssOptions: {
plugins: [
autoprefixer({ browsers })
]
},
sourceMap: false
}
}
];
const baseConfig = {
mode: isDev ? 'development' : 'production',
context: path.resolve(configs.folders.PROJECT_DIR),
entry: {
app: [
path.resolve(configs.folders.PROJECT_DIR, './src/.fes/fes.js')
]
},
resolve: {
extensions: ['.js', '.jsx', '.vue', '.json'],
alias: {
projectRoot: configs.folders.PROJECT_DIR,
'@': path.resolve(configs.folders.PROJECT_DIR, 'src'),
assets: path.resolve(configs.folders.PROJECT_DIR, './src/assets/')
}
},
output: {
globalObject: 'this',
filename: isDev ? 'js/[name].js' : 'js/[name].[contenthash:8].js',
chunkFilename: isDev ? 'js/[name].chunk.js' : 'js/[name].[contenthash:8].js',
path: configs.folders.PROJECT_DIST_DIR,
publicPath: isDev ? '/' : './'
},
module: {
// noParse: /^(vue|vue-router|vuex|vuex-router-sync|axios)$/,
// noParse: /fes-runtime/,
rules: [
/* config.module.rule('vue') */
{
test: /\.vue$/,
use: [
{
loader: require.resolve('cache-loader'),
options: {
cacheDirectory: path.resolve(configs.folders.PROJECT_DIR, 'node_modules/.cache/vue-loader')
}
},
{
loader: require.resolve('vue-loader'),
options: {
shadowMode: true,
cacheDirectory: path.resolve(configs.folders.PROJECT_DIR, 'node_modules/.cache/vue-loader'),
babelParserPlugins: ['jsx', 'classProperties', 'decorators-legacy']
}
}
]
},
/* config.module.rule('images') */
{
test: /\.(png|jpe?g|gif|webp)(\?.*)?$/,
use: [
{
loader: require.resolve('url-loader'),
options: {
limit: 4096,
fallback: {
loader: require.resolve('file-loader'),
options: {
name: isDev ? 'img/[name].[ext]' : 'img/[name].[hash:8].[ext]'
}
}
}
}
]
},
/* config.module.rule('svg') */
{
test: /\.(svg)(\?.*)?$/,
use: [
{
loader: require.resolve('file-loader'),
options: {
name: isDev ? 'img/[name].[ext]' : 'img/[name].[hash:8].[ext]'
}
}
]
},
/* config.module.rule('media') */
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
use: [
{
loader: require.resolve('url-loader'),
options: {
limit: 4096,
fallback: {
loader: require.resolve('file-loader'),
options: {
name: isDev ? 'media/[name].[ext]' : 'media/[name].[hash:8].[ext]'
}
}
}
}
]
},
/* config.module.rule('fonts') */
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
use: [
{
loader: require.resolve('url-loader'),
options: {
limit: 4096,
fallback: {
loader: require.resolve('file-loader'),
options: {
name: isDev ? 'fonts/[name].[ext]' : 'fonts/[name].[hash:8].[ext]'
}
}
}
}
]
},
/* config.module.rule('css') */
{
test: /\.css$/,
use: cssloaders
},
/* config.module.rule('postcss') */
{
test: /\.p(ost)?css$/,
use: cssloaders
},
/* config.module.rule('less') */
{
test: /\.less$/,
use: cssloaders.concat([
{
loader: require.resolve('less-loader'),
options: {
sourceMap: false,
javascriptEnabled: true
}
}
])
},
/* config.module.rule('stylus') */
{
test: /\.styl(us)?$/,
use: cssloaders.concat([
{
loader: require.resolve('stylus-loader'),
options: {
sourceMap: false,
preferPathResolver: 'webpack'
}
}
])
},
/* config.module.rule('js') */
{
test: /\.m?jsx?$/,
include(filePath) {
if (filePath.startsWith(path.resolve(process.cwd(), 'src'))) {
return true;
}
if (/fes-core.?src/.test(filePath)) {
return true;
}
if (/fes-plugin-[a-z-]+.?(src|index)/.test(filePath)) {
return true;
}
return false;
},
use: [
{
loader: require.resolve('cache-loader'),
options: {
cacheDirectory: path.resolve(configs.folders.PROJECT_DIR, 'node_modules/.cache/babel-loader')
}
},
{
loader: require.resolve('thread-loader')
},
{
loader: require.resolve('babel-loader'),
options: {
presets,
plugins
}
}
]
}
]
},
devtool: isDev && 'cheap-module-eval-source-map',
plugins: [
/* config.plugin('progress') */
new webpack.ProgressPlugin(),
/* config.plugin('vue-loader') */
new VueLoaderPlugin(),
/* config.plugin('define') */
new webpack.DefinePlugin({
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
'process.env': {
// NODE_ENV: isDev ? 'development' : 'production',
env: JSON.stringify(configs.env),
command: JSON.stringify(configs.command)
}
}),
/* config.plugin('clean dist') */
isBuild && new CleanWebpackPlugin(),
/* config.plugin('extract-css') */
isBuild
&& new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].css'
}),
/* config.plugin('Copy static') */
isBuild
&& new CopyPlugin([
{
from: configs.folders.PROJECT_STATIC_DIR,
to: path.resolve(
configs.folders.PROJECT_DIST_DIR,
'static'
)
}
]),
/* config.plugin('optimize-css') */
isBuild
&& new OptimizeCssnanoPlugin({
sourceMap: false,
cssnanoOptions: {
preset: [
'default',
{
mergeLonghand: false,
cssDeclarationSorter: false
}
]
}
}),
/* config.plugin('hash-module-ids') */
isBuild
&& new webpack.HashedModuleIdsPlugin({
hashDigest: 'hex'
}),
/* config.plugin('固定一下 chunk id') */
isBuild
&& new webpack.NamedChunksPlugin((chunk) => {
if (chunk.name) {
return chunk.name;
}
// eslint-disable-next-line
const hash = require('hash-sum');
const joinedHash = hash(
Array.from(chunk.modulesIterable, m => m.id).join('_')
);
return `chunk-${joinedHash}`;
}),
// /* config.plugin('Copyright') */
// isBuild
// && new webpack.BannerPlugin(''),
/* config.plugin('case-sensitive-paths') */
new CaseSensitivePathsPlugin(),
/* config.plugin('friendly-errors') */
new FriendlyErrorsPlugin(),
isBuild && gzipCompress && new CompressionWebpackPlugin({ // gzip 压缩
filename: '[path][base].gz',
test: /\.js$|\.html$|\.css/,
threshold: 10240,
minRatio: 0.8,
...gzipCompress
}),
/* config.plugin('index.html') */
new HtmlPlugin({
template,
minify: isBuild && {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
collapseBooleanAttributes: true,
removeScriptTypeAttributes: true
}
})
]
};
if (isBuild) {
baseConfig.optimization = {
minimizer: [
new TerserPlugin({
test: /\.m?js(\?.*)?$/i,
chunkFilter: () => true,
warningsFilter: () => true,
extractComments: false,
sourceMap: true,
cache: true,
cacheKeys: defaultCacheKeys => defaultCacheKeys,
parallel: true,
include: undefined,
exclude: undefined,
minify: undefined,
terserOptions: {
output: {
comments: /^\**!|@preserve|@license|@cc_on/i
},
compress: {
arrows: false,
collapse_vars: false,
comparisons: false,
computed_props: false,
hoist_funs: false,
hoist_props: false,
hoist_vars: false,
inline: false,
loops: false,
negate_iife: false,
properties: false,
reduce_funcs: false,
reduce_vars: false,
switches: false,
toplevel: false,
typeofs: false,
booleans: true,
if_return: true,
sequences: true,
unused: true,
conditionals: true,
dead_code: true,
evaluate: true
},
mangle: {
safari10: true
}
}
})
],
splitChunks: {
cacheGroups: {
vendors: {
name: 'chunk-vendors',
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: 'chunk-common',
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
}
},
runtimeChunk: true
};
}
baseConfig.plugins = baseConfig.plugins.filter(plu => plu !== false);
let advancedConfig = {};
const projectWebpackConfigFile = path.resolve(configs.folders.PROJECT_DIR, 'webpack.config.js');
if (fs.existsSync(projectWebpackConfigFile)) {
console.log('[init] 加载项目个性webpack配置文件');
// eslint-disable-next-line
advancedConfig = require(projectWebpackConfigFile)(mode, configs, webpack);
}
return merge(baseConfig, advancedConfig);
};