总结
至此,关于本次官网改版的技术分享基本告一段落,我基本上分享了我本项目的所有收获。这里再总结一下:
- 使用 Webpack 打包
- 使用多个 html-wepback-plugin 输出多 HTML 页面
- 使用 Pug 写模板
- 利用 Pug 支持内建函数的方式实现 i18n
- 使用 Stylus 写样式
- 基于 Bootstrap 的布局系统完成响应式
- 利用 Chrome 内建的 Audit 工具检查网站,并修复其中不好的地方
- 利用 html-webpack-plugin 的钩子开发插件,利用浏览器新特性。
常见问题
Webapck 应该装在哪儿?
答:项目中直接使用 Webpack 的机会不多,所以全局安装也可以,而且比较省硬盘。不过我还是建议大家装在项目本地,这样不同的项目如果依赖不同的工具链,比较方便共存。
能看下真实项目么?
答:项目本身属于我厂,业务代码不便公开。跟本文相关的配置文件,我放在下面:
webpack.config.js
const path = require('path');
const {promises: {readFile}, existsSync} = require('fs');
const {DefinePlugin} = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const po2json = require('po2json');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const {isArray, isString} = require('lodash');
const DEV = require('../config/dev');
/* global __dirname */
const devMode = process.env.NODE_ENV !== 'production';
const lang = process.env.TARGET_LANG || 'en';
console.log('Current mode: ', devMode ? 'Development' : 'Production');
console.log('Current language: ', lang);
const destDir = path.resolve(__dirname, `../../${lang}`);
const translation = require(`../lang/${lang}`);
let po;
global.__ = function (value) {
if (!po || !po[value]) {
return value;
}
let translation = po[value];
translation = isArray(translation) ? translation.join('') : translation;
return translation || value;
};
const config = {
context: path.resolve(__dirname, '../js'),
entry: {
index: './index.js',
edge: './edge',
about: './about.js',
comparison: './about.js',
contact: './about.js',
forget: './forget.js',
login: './login.js',
logout: './logout.js',
price: './price.js',
profile: './profile.js',
register: './register.js',
reset: './reset.js',
trial: './trial.js',
verify: './verify.js',
},
output: {
path: destDir,
filename: '[name].js',
publicPath: devMode ? '/' : `/${lang}/`,
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.vue$/,
loader: 'vue-loader',
},
{
test: /(pages|template)\/[a-zA-Z0-9-]+\.pug$/,
loader: 'pug-loader',
},
{
test: /\.pug$/,
loader: 'pug-plain-loader',
exclude: /(pages|template)\//,
},
{
test: /\.styl(us)?$/,
use: [
'style-loader',
'css-loader',
'stylus-loader',
],
},
{
test: /\.(png|jpg|gif|svg|woff2|eot|woff|ttf|ico)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'assets',
publicPath: devMode ? '/assets' : `/${lang}/assets`,
},
},
],
},
],
},
devtool: 'source-map',
mode: 'development',
resolve: {
extensions: ['.js', '.json', '.vue'],
},
plugins: [
new DefinePlugin(DEV),
new VueLoaderPlugin(),
],
externals: {
'swiper': 'Swiper',
'mediaelement': 'MediaElementPlayer',
'jquery': 'jQuery',
'vue': 'Vue',
'axios': 'axios',
},
devServer: {
port: 8080,
},
};
const pages = Object.keys(config.entry);
config.plugins = config.plugins.concat(pages.map(page => {
return new HtmlWebpackPlugin({
filename: page === 'index' ? `${page}.html` : `${page}/index.html`,
template: path.resolve(__dirname, `../pages/${page}.pug`),
chunks: [page],
templateParameters: {
...translation,
lang,
page,
devMode,
},
});
}));
module.exports = Promise.resolve()
.then(() => {
const poPath = path.resolve(__dirname, `../lang/${lang}.po`);
if (!existsSync(poPath)) {
return Promise.reject('PO file not founded.');
}
return readFile(poPath, 'utf8');
})
.then(poData => {
po = po2json.parse(poData);
})
.catch(error => {
if (isString(error)) {
console.log('[Log]', error);
return;
}
throw error;
})
.then(() => {
return config;
});
webpack.config.i18n.js
const path = require('path');
const {cloneDeep} = require('lodash');
let config = require('./webpack.config');
/* global __dirname */
module.exports = config
.then(config => {
config = cloneDeep(config);
config.output.path = path.resolve(__dirname, '../tmp/');
return config;
});
webpack.config.prod.js
const {defaults} = require('lodash');
const {DefinePlugin} = require('webpack');
const TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const LazyLoadPlugin = require('./lazyload');
const base = require('./webpack.config');
const config = require('../config/prod');
/* global __dirname */
module.exports = base.then(base => {
base.plugins[0] = new DefinePlugin(config);
return defaults({
mode: 'production',
devtool: false,
module: {
rules: base.module.rules.map(rule => {
if (!rule.test.test('a.styl')) {
return rule;
}
rule.use[0] = MiniCssExtractPlugin.loader;
return rule;
}),
},
plugins: [new CleanWebpackPlugin()].concat(
base.plugins,
[
new MiniCssExtractPlugin({
filename: 'screen.css',
}),
new LazyLoadPlugin(),
]
),
optimization: {
minimizer: [
new OptimizeCSSAssetsPlugin(),
new TerserPlugin({
cache: true,
parallel: true,
exclude: /node_modules/,
terserOptions: {
ecma: 7,
toplevel: true,
compress: {
drop_console: true,
},
},
}),
],
},
}, base);
});