记一次老项目添加webpack的过程
目录结构
-
改造前
-
改造后
为什么要改造?老项目痛点在哪里?
- 老项目技术栈使用纯原生,需要兼容IE10,没有引入帮助插件
- 老项目css与js没有通过postcss与babel进行转化 容易产生兼容错误
- 老项目特殊需求静态资源如图片、图标、等需要打包到js文件中,每次新增都要自己转化,很麻烦
- 老项目JS代码全冗杂在一起,不模块化,不够清晰。
- 老项目与自动化打包构建工具合作不太方便
- 老项目没有使用scss等css工具
- 老项目没有使用eslint 也没有格式化代码 乱飞你懂得
- ……
改造之后的package.json
{
"name": "aiews_js",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
// dev环境 mode作为环境标识
"serve": "webpack serve --mode=development",
// 生产环境
"build": "webpack --mode=production"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.13.8",
"@babel/plugin-transform-runtime": "^7.13.9",
"@babel/preset-env": "^7.13.9",
"@commitlint/cli": "^12.0.1",
"@commitlint/config-conventional": "^12.0.1",
"autoprefixer": "^10.2.4",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.2.2",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^5.1.1",
"eslint": "^7.21.0",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^7.7.0",
"eslint-webpack-plugin": "^2.5.2",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.2.0",
"husky": "^5.1.3",
"lint-staged": "^10.5.4",
"node-sass": "^4.14.1",
"postcss-loader": "^5.0.0",
"postcss-nested": "^5.0.5",
"prettier": "^2.2.1",
"sass-loader": "^11.0.1",
"style-loader": "^2.0.0",
"terser-webpack-plugin": "^5.1.1",
"url-loader": "^4.1.1",
"webpack": "^5.24.3",
"webpack-cli": "^4.5.0",
"webpack-dev-server": "^3.11.2"
},
"dependencies": {
"@babel/runtime": "^7.13.9"
},
// git提交的验证
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"lint-staged": {
"*.{js,html}": [
"npm run lint",
"git add"
]
}
}
webpack.config.js详解
// 需求暂时就这么多 其实为了精简 模块化 可以采用merge合并配置
// html提取插件 直接影响pro和dev环境的的html模板
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装
const webpack = require('webpack'); // 访问内置的插件
const path = require('path');
// 每次编译、打包清除dist目录
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
// 压缩插件
const TerserPlugin = require('terser-webpack-plugin');
// eslint
const ESLintPlugin = require('eslint-webpack-plugin');
const resolve = function (dir) {
return path.join(__dirname, dir);
};
// app目录
const APP_PATH = './src/app';
const config = {
// 构建目标 默认是node 要改成web 热更新才有用
// 而且target影响着输出代码的语法格式 如果项目里有browerslistrc 建议使用这个
// 发现browserslist貌似不能触发热更新 所以在下面通过环境来动态使用target了
// 如果设置成web 打包之后的webpack相关代码还是会有es6语法的 所以生产环境要使用browserslist作为target
// target: 'browserslist',
// 项目入口文件
// 一开始的需求只有一个入口 后来需要演示 又搞了个演示的入口
// entry 的 key 就是不同入口的chunk 名字
// 这个名字和HtmlWebpackPlugin插件中的chunks对应上就可以将每个入口引入的资源打包到对应的目录中
entry: {
// 入口1
render: `${APP_PATH}/main/index.js`,
// 入口2
demonstration: `${APP_PATH}/demonstration/index.js`,
},
// 输出 就是构建之后的配置
output: {
// [name] 是个变量 对应entry中的key
// 例如入口1的key是 `render` 那么他的输出就是 render.js
// 可以配置[hash]让每次生成的包名都不一样 避免缓存 我这里需求使然 需要固定的名字
filename: '[name].js',
path: path.resolve(__dirname, 'dist'),
scriptType: 'text/javascript',
},
module: {
// 需要的一些loader
rules: [
{
// js使用babelloader 将代码转化为目录中 `.browserslistrc` 文件中的版本
// 目录中有个 `babel.config.js` 这个使用按需引入的写法就行了 如果有特殊需求可以显示引入 避免babel-polyfill完整引入 导致包过大 这个下面会介绍按需引入的写啊
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: ['babel-loader'],
},
// 图片资源的loader 因为项目特殊需求 我们需要将图片打包进script中 还好图片比较小
//
{
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
},
},
],
},
// sass-loader的配置就可以让我们写scss了 项目需求使然 不需要提取css成文件 都放入到js中
{
test: /\.scss$/,
use: [
// 将 JS 字符串生成为 style 节点
{
loader: 'style-loader',
},
// 将 CSS 转化成 CommonJS 模块
{
loader: 'css-loader',
},
// 这个就是添加兼容的插件 依赖于 postcss.config.js 还有 .browserslistrc
'postcss-loader',
// 将 Sass 编译成 CSS
{
loader: 'sass-loader',
},
],
},
],
},
// 这个配置是项目路径的别名
resolve: {
alias: {
'@': resolve('src'),
'@js': resolve('src/js'),
'@scss': resolve('src/scss'),
},
},
// 使用的插件
plugins: [
// 打包过程的输出状态显示插件
new webpack.ProgressPlugin(),
// 每次打包前清空目录插件
new CleanWebpackPlugin(),
// eslint插件 fix true 自动更正格式
new ESLintPlugin({ fix: true }),
// 入口1的html提取插件
new HtmlWebpackPlugin({
// html模板
template: `${APP_PATH}/main/index.html`,
// 输出的文件名
filename: 'index.html',
// chunks 就是要打包进来的js render对应entry中的render
chunks: ['render'],
// 项目需求使然 如果不加这个 script 会加上defer 影响执行顺序
scriptLoading: 'blocking',
// script插入在html中的哪个位置
inject: 'head',
}),
// 入口2 配置同上
new HtmlWebpackPlugin({
template: `${APP_PATH}/demonstration/index.html`,
filename: 'demonstration.html',
chunks: ['demonstration'],
scriptLoading: 'blocking',
inject: 'head',
}),
],
devServer: {
host: '0.0.0.0',
// 静态资源文件夹
contentBase: path.join(__dirname, 'public'), // boolean | string | array, static file location
// 热更新
hot: true, // hot module replacement. Depends on HotModuleReplacementPlugin
// 不显示info信息
noInfo: true, // only errors & warns on hot reload
// 启动后是否自动打开浏览器访问该项目
open: true,
// 如果打开了 访问哪个页面
openPage: 'index.html',
compress: true,
overlay: true,
inline: true,
// ...
},
};
module.exports = (env, argv) => {
// 不同的环境使用不同的配置 这个是开发环境 可以通过插件给项目中注入环境变量
if (argv.mode === 'development') {
config.devtool = 'source-map';
config.target = 'web';
}
// 这是生产模式
if (argv.mode === 'production') {
//... 生产模式启动压缩
config.optimization = {
minimize: true,
minimizer: [new TerserPlugin()],
};
config.target = 'browserslist';
}
return config;
};
.browserslistrc
> 1%,
last 2 versions,
not ie <= 8,
safari >= 7
.eslintrc.js
module.exports = {
// 使用哪个插件
plugins: ['prettier'],
// 使用的核心
parser: 'babel-eslint',
// 自定义规则
rules: {
quotes: ['error', 'single'],
'prettier/prettier': [
'error',
{ singleQuote: true },
{ trailingComma: 'none' },
],
},
};
babel.config.js
module.exports = {
presets: [['@babel/preset-env']],
plugins: ['@babel/plugin-transform-runtime'],
};
postcss.config.js
module.exports = {
plugins: [require('autoprefixer'), require('postcss-nested')],
};
commitlint.config.js
这个就是git提交钩子插件 需要eslint的配合去做验证
module.exports = {
extends: ['@commitlint/config-conventional'],
};
单入口的webpack配置栗子
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装
const webpack = require('webpack'); // 访问内置的插件
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const ESLintPlugin = require('eslint-webpack-plugin');
const config = {
// 构建目标 默认是node 要改成web 热更新才有用
// 而且target影响着输出代码的语法格式 如果项目里有browerslistrc 建议使用这个
target: 'browserslist',
entry: './src/main.js',
output: {
filename: 'render.js',
path: path.resolve(__dirname, 'dist'),
scriptType: 'text/javascript',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: ['babel-loader'],
},
{
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
},
},
],
},
{
test: /\.scss$/,
use: [
// 将 JS 字符串生成为 style 节点
{
loader: 'style-loader',
},
// 将 CSS 转化成 CommonJS 模块
{
loader: 'css-loader',
},
'postcss-loader',
// 将 Sass 编译成 CSS
{
loader: 'sass-loader',
},
],
},
],
},
plugins: [
new webpack.ProgressPlugin(),
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
scriptLoading: 'blocking',
inject: 'head',
}),
new ESLintPlugin({ fix: true }),
],
devServer: {
host: '0.0.0.0',
contentBase: path.join(__dirname, 'public'), // boolean | string | array, static file location
hot: true, // hot module replacement. Depends on HotModuleReplacementPlugin
noInfo: true, // only errors & warns on hot reload
open: true,
compress: true,
overlay: true,
inline: true,
// ...
},
};
module.exports = (env, argv) => {
if (argv.mode === 'development') {
config.devtool = 'source-map';
// config.target = 'browserslist';
}
if (argv.mode === 'production') {
//...
config.optimization = {
minimize: true,
minimizer: [new TerserPlugin()],
};
// config.target = 'browserslist';
}
return config;
};
总结
- webpack的集成在前端自动化构建上真的省心省力,而且新版本编译速度很快,上手也比较简单,非常好用!