上一篇我们说到了gulp任务流,这一篇继续说
查看package.json,可以看到对于antd-tools用到了以下几个命令:
"lint:ts": "npm run tsc && antd-tools run ts-lint"
"lint-fix:ts": "npm run tsc && antd-tools run ts-lint-fix"
"dist": "antd-tools run dist"
"compile": "antd-tools run compile"
"predeploy"
pub
prepublish
npm run lint:ts
:编译typescript并检测typescript语法规范;
npm run lint-fix:ts
:编译typescript并检测typescript语法规范并且根据配置文件自动格式化代码;
npm run dist
:
首先看dist任务的源码
function dist(done) {
rimraf.sync(path.join(cwd, 'dist'));
process.env.RUN_ENV = 'PRODUCTION';
const webpackConfig = require(path.join(cwd, 'webpack.config.js'));
webpack(webpackConfig, (err, stats) => {
if (err) {
console.error(err.stack || err);
if (err.details) {
console.error(err.details);
}
return;
}
const info = stats.toJson();
if (stats.hasErrors()) {
console.error(info.errors);
}
if (stats.hasWarnings()) {
console.warn(info.warnings);
}
const buildInfo = stats.toString({
colors: true,
children: true,
chunks: false,
modules: false,
chunkModules: false,
hash: false,
version: false,
});
console.log(buildInfo);
done(0);
});
}
简单概括来说就是根据项目目录的webpack.config.js让webpack去处理前端资源打包工作,并输出必要信息。下面是webpack.config.js源码:
// This config is for building dist files
const webpack = require('webpack');
const getWebpackConfig = require('antd-tools/lib/getWebpackConfig');
const WebpackBar = require('webpackbar');
// noParse still leave `require('./locale' + name)` in dist files
// ignore is better
// http://stackoverflow.com/q/25384360
function ignoreMomentLocale(webpackConfig) {
delete webpackConfig.module.noParse;
webpackConfig.plugins.push(new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/));
}
function addLocales(webpackConfig) {
let packageName = 'antd-with-locales';
if (webpackConfig.entry['antd.min']) {
packageName += '.min';
}
webpackConfig.entry[packageName] = './index-with-locales.js';
webpackConfig.output.filename = '[name].js';
}
function externalMoment(config) {
config.externals.moment = {
root: 'moment',
commonjs2: 'moment',
commonjs: 'moment',
amd: 'moment',
};
}
function usePrettyWebpackBar(config) {
// remove old progress plugin.
config.plugins = config.plugins
.filter((plugin) => {
return !(plugin instanceof webpack.ProgressPlugin)
&& !(plugin instanceof WebpackBar);
});
// use brand new progress bar.
config.plugins.push(
new WebpackBar({
name: '📦 Webpack',
minimal: false,
})
);
}
const webpackConfig = getWebpackConfig(false);
if (process.env.RUN_ENV === 'PRODUCTION') {
webpackConfig.forEach((config) => {
ignoreMomentLocale(config);
externalMoment(config);
addLocales(config);
usePrettyWebpackBar(config);
});
}
module.exports = webpackConfig;
可以看到这里针对生产环境,进行了以下调整:
1.忽略momenjs本地化模块
2.对于momentjs不进行打包,同时设定全局变量,在runtime通过外部引入
3.增加蚂蚁金服antd自己的本地化模块
4.使用更美观的webpack进度条
下面再来看看'antd-tools/lib/getWebpackConfig'
,这里是ant-design的webpack核心配置:
const path = require('path');
const webpack = require('webpack');
const WebpackBar = require('webpackbar');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const deepAssign = require('deep-assign');
const replaceLib = require('./replaceLib');
const postcssConfig = require('./postcssConfig');
module.exports = function (modules) {
const pkg = require(path.join(process.cwd(), 'package.json'));
const babelConfig = require('./getBabelCommonConfig')(modules || false);
const pluginImportOptions = [
{
style: true,
libraryName: pkg.name,
libraryDirectory: 'components',
},
];
if (pkg.name !== 'antd') {
pluginImportOptions.push({
style: 'css',
libraryDirectory: 'es',
libraryName: 'antd',
});
}
babelConfig.plugins.push([
require.resolve('babel-plugin-import'),
pluginImportOptions,
]);
if (modules === false) {
babelConfig.plugins.push(replaceLib);
}
const config = {
devtool: 'source-map',
output: {
path: path.join(process.cwd(), './dist/'),
filename: '[name].js',
},
resolve: {
modules: ['node_modules', path.join(__dirname, '../node_modules')],
extensions: [
'.web.tsx',
'.web.ts',
'.web.jsx',
'.web.js',
'.ts',
'.tsx',
'.js',
'.jsx',
'.json',
],
alias: {
[pkg.name]: process.cwd(),
},
},
node: [
'child_process',
'cluster',
'dgram',
'dns',
'fs',
'module',
'net',
'readline',
'repl',
'tls',
].reduce((acc, name) => Object.assign({}, acc, { [name]: 'empty' }), {}),
module: {
noParse: [/moment.js/],
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: babelConfig,
},
{
test: /\.tsx?$/,
use: [
{
loader: 'babel-loader',
options: babelConfig,
},
{
loader: 'ts-loader',
options: {
transpileOnly: true,
},
},
],
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: [
{
loader: 'css-loader',
options: {
sourceMap: true,
},
},
{
loader: 'postcss-loader',
options: Object.assign(
{},
postcssConfig,
{ sourceMap: true }
),
},
],
}),
},
{
test: /\.less$/,
use: ExtractTextPlugin.extract({
use: [
{
loader: 'css-loader',
options: {
sourceMap: true,
},
},
{
loader: 'postcss-loader',
options: Object.assign(
{},
postcssConfig,
{ sourceMap: true }
),
},
{
loader: 'less-loader',
options: {
sourceMap: true,
},
},
],
}),
},
],
},
plugins: [
new ExtractTextPlugin({
filename: '[name].css',
disable: false,
allChunks: true,
}),
new CaseSensitivePathsPlugin(),
new webpack.BannerPlugin(`
${pkg.name} v${pkg.version}
Copyright 2015-present, Alipay, Inc.
All rights reserved.
`),
new WebpackBar({
name: '📦 Webpack',
minimal: false,
}),
],
};
if (process.env.RUN_ENV === 'PRODUCTION') {
const entry = ['./index'];
config.entry = {
[`${pkg.name}.min`]: entry,
};
config.externals = {
react: {
root: 'React',
commonjs2: 'react',
commonjs: 'react',
amd: 'react',
},
'react-dom': {
root: 'ReactDOM',
commonjs2: 'react-dom',
commonjs: 'react-dom',
amd: 'react-dom',
},
};
config.output.library = pkg.name;
config.output.libraryTarget = 'umd';
const uncompressedConfig = deepAssign({}, config);
config.plugins = config.plugins.concat([
new webpack.optimize.UglifyJsPlugin({
output: {
ascii_only: true,
},
compress: {
warnings: false,
},
}),
new webpack.optimize.ModuleConcatenationPlugin(),
new webpack.LoaderOptionsPlugin({
minimize: true,
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production'),
}),
]);
uncompressedConfig.entry = {
[pkg.name]: entry,
};
uncompressedConfig.plugins.push(new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development'),
}));
return [config, uncompressedConfig];
}
return config;
};
下面是对antd-tools的webpack配置文件的解析
首先:const pkg = require(path.join(process.cwd(), 'package.json'));
,我们拿到项目的package.json文件,然后通过getBabelCommonConfig
函数拿到babel的配置,babel配置如下:
'use strict';
module.exports = function (modules) {
const plugins = [
require.resolve('babel-plugin-transform-es3-member-expression-literals'),
require.resolve('babel-plugin-transform-es3-property-literals'),
require.resolve('babel-plugin-transform-object-assign'),
require.resolve('babel-plugin-transform-class-properties'),
require.resolve('babel-plugin-transform-object-rest-spread'),
];
plugins.push([require.resolve('babel-plugin-transform-runtime'), {
polyfill: false,
}]);
return {
presets: [
require.resolve('babel-preset-react'),
[require.resolve('babel-preset-env'), {
modules,
targets: {
browsers: [
'last 2 versions',
'Firefox ESR',
'> 1%',
'ie >= 9',
'iOS >= 8',
'Android >= 4',
],
},
}],
],
plugins,
};
};
之后针对拿到的babel配置,添加并配置babel-plugin-import
插件;
用过ant-design的前端工程师们应该都知道这是一个按需加载的babel插件,有了它你就可以加载相应使用的组件的样式和js,而不是将ant-design全部加载打包,节省了打包后的体积。
之后是一个骚操作:
if (modules === false) {
babelConfig.plugins.push(replaceLib);
}
这个操作用node-modules中的.js文件去替换项目中引用的库文件;
下面是replaceLib代码:(用到了高深的babel的AST知识)
'use strict';
const { join, dirname } = require('path');
const fs = require('fs');
const cwd = process.cwd();
function replacePath(path) {
if (path.node.source && /\/lib\//.test(path.node.source.value)) {
const esModule = path.node.source.value.replace('/lib/', '/es/');
const esPath = dirname(join(cwd, `node_modules/${esModule}`));
if (fs.existsSync(esPath)) {
path.node.source.value = esModule;
}
}
}
function replaceLib() {
return {
visitor: {
ImportDeclaration: replacePath,
ExportNamedDeclaration: replacePath,
},
};
}
module.exports = replaceLib;
后面是正式的webpack配置了,比较常规,挑几个有特点的说说:
transpileOnly: true,
:tsx编译成jsx或js时,不写入文件,保存在内存中,加快编译速度。
对于样式文件,antd-tools还使用了postcssConfig
,以下是它的配置
const rucksack = require('rucksack-css');
const autoprefixer = require('autoprefixer');
module.exports = {
plugins: [
rucksack(),
autoprefixer({
browsers: [
'last 2 versions',
'Firefox ESR',
'> 1%',
'ie >= 9',
'iOS >= 8',
'Android >= 4',
],
}),
],
};
可以看到它用了rucksack插件,可以很方便的写css了.
在plugins中还使用了CaseSensitivePathsPlugin
:打包时赋予变量提升优化浏览器运行性能;
new webpack.BannerPlugin
:在每个打包出来的文件写上版权信息;
别的配置都是常规操作,还是很容易看懂的。