san-cli-service
Version:
定制化的前端工程构建工具
191 lines (182 loc) • 8.24 kB
JavaScript
/**
* Copyright (c) Baidu Inc. All rights reserved.
*
* This source code is licensed under the MIT license.
* See LICENSE file in the project root for license information.
*
* @file base webpack config
* @author ksky521
*/
const path = require('path');
const resolve = require('resolve');
const defaultsDeep = require('lodash.defaultsdeep');
const fs = require('fs');
// 相对 service module 的路径
function resolveLocal(...args) {
return path.join(__dirname, '../', ...args);
}
module.exports = {
id: 'built-in:base',
apply(api, projectOptions) {
api.chainWebpack(webpackConfig => {
const isProd = api.isProd();
// 是 modern 模式,但不是 modern 打包,那么 js 加上 legacy
const isLegacyBundle = process.env.SAN_CLI_LEGACY_BUILD;
// set mode
webpackConfig.mode(isProd ? 'production' : 'development').context(api.service.cwd);
// set output
// prettier-ignore
webpackConfig.output
.path(api.resolve(projectOptions.outputDir))
// 留个小彩蛋吧~
.jsonpFunction(projectOptions.jsonpFunction || 'HK3')
/* eslint-disable max-len */
.filename((isLegacyBundle ? '[name]-legacy' : '[name]') + `${projectOptions.filenameHashing ? '.[contenthash:8]' : ''}.js`)
/* eslint-enable max-len */
.publicPath(projectOptions.publicPath);
// prettier-ignore
/* eslint-disable*/
webpackConfig
.resolve
.set('symlinks', false)
// 默认加上 less 吧,less 内部用的最多
.extensions.merge(['.js', '.css', '.less', '.san'])
.end()
.plugin('pnp')
.use({...require('pnp-webpack-plugin')})
.end()
.modules
.add('node_modules')
.add(api.resolve('node_modules'))
.add(resolveLocal('node_modules'))
.end()
// set alias
.alias
.set('core-js', path.dirname(require.resolve('core-js')))
.set('regenerator-runtime', path.dirname(require.resolve('regenerator-runtime')));
if (fs.existsSync(api.resolve('src'))) {
webpackConfig.resolve.alias
// 加个@默认值
.set('@', api.resolve('src'));
}
// prettier-ignore
// set resolveLoader
const resolveLoader = webpackConfig
.resolveLoader
// 添加 pnp-loader
.plugin('pnp-loaders')
.use({ ...require('pnp-webpack-plugin').topLevelLoader })
.end()
.modules
.add('node_modules')
.add(api.resolve('node_modules'))
.add(resolveLocal('node_modules'));
// 优先考虑本地安装的html-loader版本,没有的话去全局路径寻找
try {
const htmlLoader = resolve.sync('html-loader', {basedir: api.getCwd()});
// console.log(`Find local htmlLoader at [${htmlLoader}]`);
}
catch (e) {
// 找到html-loader所在根目录
let nodeModulesPath = path.dirname(require.resolve('html-loader'));
nodeModulesPath = nodeModulesPath.substring(0, nodeModulesPath.lastIndexOf('node_modules')) + 'node_modules';
resolveLoader.add(nodeModulesPath);
}
/* eslint-enable */
// set san alias
try {
const sanFile = resolve.sync('san', {basedir: api.getCwd()});
const sanPath = path.dirname(sanFile);
webpackConfig.resolve.alias.set('san', path.join(sanPath, !isProd ? 'san.spa.dev.js' : 'san.spa.js'));
}
catch (e) {
const sanPath = path.dirname(require.resolve('san'));
webpackConfig.resolve.alias.set('san', path.join(sanPath, !isProd ? 'san.spa.dev.js' : 'san.spa.js'));
}
// projectOptions.alias
if (projectOptions.alias) {
let alias = projectOptions.alias;
Object.keys(alias).forEach(k => {
let p = path.isAbsolute(alias[k]) ? alias[k] : api.resolve(alias[k]);
webpackConfig.resolve.alias.set(k, p);
});
}
// set loaders
// ------------------------loaders------------
const loaderOptions = projectOptions.loaderOptions || {};
function setLoader(lang, test, loaders, curLoaderOptions = {}) {
const baseRule = webpackConfig.module.rule(lang).test(test);
if (!Array.isArray(loaders)) {
loaders = [loaders];
}
loaders.forEach(loaderName => {
curLoaderOptions = defaultsDeep(curLoaderOptions, loaderOptions[loaderName] || {});
const {loader, options, name} = require(`./loaders/${loaderName}`)(
curLoaderOptions,
projectOptions,
api
);
baseRule
.use(name)
.loader(loader)
.options(options)
.end();
});
}
if (!isProd) {
setLoader('san', /\.san$/, ['hmr', 'san']);
setLoader('js', /\.m?js?$/, ['hmr']);
}
else {
setLoader('san', /\.san$/, ['san']);
}
setLoader('ejs', /\.ejs$/, 'ejs');
setLoader('html', /\.html?$/, 'html');
setLoader('svg', /\.svg(\?.*)?$/, 'svg', {
dir: 'svg'
});
setLoader('img', /\.(png|jpe?g|gif|webp)(\?.*)?$/, 'url', {
dir: 'img'
});
setLoader('media', /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 'url', {
dir: 'media'
});
setLoader('fonts', /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, 'url', {
dir: 'fonts'
});
// ----------------------pulgins---------------------
webpackConfig.plugin('san').use(require('san-loader/lib/plugin'));
// 大小写敏感!!!!
webpackConfig.plugin('case-sensitive-paths').use(require('case-sensitive-paths-webpack-plugin'));
// 定义 env 中的变量
// 这里需要写到文档,以 SAN_VAR 开头的 env 变量
webpackConfig.plugin('define').use(require('webpack/lib/DefinePlugin'), [defineVar()]);
if (!isProd) {
// dev mode
webpackConfig.devtool('cheap-module-eval-source-map');
webpackConfig.plugin('hmr')
.use(require('webpack/lib/NamedModulesPlugin'))
.use(require('webpack/lib/HotModuleReplacementPlugin'));
webpackConfig.plugin('no-emit-on-errors').use(require('webpack/lib/NoEmitOnErrorsPlugin'));
}
// 将 env 中的值进行赋值
function defineVar() {
const vars = {
// TODO 这里要不要按照 mode 设置下 undefined 的情况?
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
PRODUCTION: JSON.stringify(isProd),
BASE_URL: JSON.stringify(projectOptions.publicPath)
};
// 这里把var 变量名拆出来
const re = /^SAN_VAR_([\w\d\_]+)$/;
Object.keys(process.env).forEach(key => {
if (re.test(key)) {
const name = re.exec(key)[1];
vars[name] = JSON.stringify(process.env[key]);
}
});
return vars;
}
});
}
};