目录
一、待解决的问题
上次初步了解webpack后待解决的问题
1.webpack相对成熟和常用的module加载和plugins的搭配组合写法?在处理较为庞大的项目中时,webpack对应的组合和写法。
2.webpack动态加载模块的实现方式和对应的基本原理?
这些在网上面有很多种解答,很庞大,还是梳理作用和需求,逐步搭建一遍吧。不然无法分辨网上大量的说法的正确与否。
二、思考相关问题的历程
2.1 看官方文档看的头大
涉及到配置的说明文字相当庞大,除了基本结构在这个过程中更清晰了一些。其它的内容,越看越头大。有很多的配置属性只是一笔带过,具体的配置内容需要到相应的loader和plugin里面去看。有种一眼望不到边的感觉,更别谈看着这玩意配置了。
直到看到由浅入深的官方文档的配置指南具体配置,才开始有了一定的头绪。
2.2 初步搭建webpack实现的目标分析和思考
(1)能够打包对应的指定文件路口的文件:包括了图片、代码文件、css文件
(2)分环境:划分开发,生产两个环境
(3)在开发环境下,可以热更新,发生改变后,自动刷新界面。在生产环境下,需要能够压缩文件,能够缓存未变更的文件。只变更已经变更了的文件。
(4)在开发环境下可以统一规范代码编写方式。明显格式错误时会报错
暂时只能想到这些,后面参考搭建好的较打项目的需求,考虑webpack相应的需要满足的新的需求。
大致的需求整理如下
2.3 关于对应版本的问题
webpack5是最新的版本,但是很多版本特性可能不稳定,所以暂时使用webpack4。从稳定性角度考虑,框架和对应版本选型均低于最新版本的1到2个版本。
React最新版本是17,现在用16.9
对应配置版本如下:
- webpack
v4
- react
v16.9
- typescript
v3.5
- babel
v7
- eslint
v6.2
三、搭建过程记录
3.1 整理一部分相关依赖的安装
这部分依赖安装,要是分步安装的话,会很容易出现版本问题。参考官方脚手架及git项目中的依赖。直接写入package.json中,然后使用npm install 完成初步的依赖安装会更快些。 后面有新的需要再单个依赖安装,package.json会自动更新。
"dependencies": {
"@loadable/component": "^5.12.0",
"core-js": "^2.6.9",
"react": "^16.13.0",
"react-dom": "^16.13.0",
"react-router-dom": "^5.1.2"
},
"devDependencies": {
"@babel/core": "^7.8.7",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/polyfill": "^7.8.7",
"@babel/preset-env": "^7.8.7",
"@babel/preset-react": "^7.8.3",
"@commitlint/cli": "^8.3.5",
"@svgr/webpack": "^5.2.0",
"@types/jest": "^25.1.4",
"@types/node": "^13.9.0",
"@types/react": "^16.9.23",
"@types/react-dom": "^16.9.5",
"@types/react-router-dom": "^5.1.3",
"@typescript-eslint/eslint-plugin": "^2.22.0",
"@typescript-eslint/parser": "^2.22.0",
"autoprefixer": "^9.7.4",
"awesome-typescript-loader": "^5.2.1",
"babel-eslint": "^10.1.0",
"babel-jest": "^25.1.0",
"babel-loader": "^8.0.6",
"chalk": "^3.0.0",
"clean-webpack-plugin": "^3.0.0",
"compression-webpack-plugin": "^3.1.0",
"copy-webpack-plugin": "^5.1.1",
"cross-env": "^7.0.2",
"css-loader": "^3.4.2",
"cssnano": "^4.1.10",
"cssnano-preset-advanced": "^4.0.7",
"dotenv": "^8.2.0",
"dotenv-expand": "^5.1.0",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.10.0",
"eslint-loader": "^3.0.3",
"eslint-plugin-jsx-control-statements": "^2.2.1",
"eslint-plugin-prettier": "^3.1.2",
"eslint-plugin-react": "^7.19.0",
"file-loader": "^5.1.0",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"husky": "^4.2.3",
"interpolate-html-plugin": "^3.0.0",
"jest": "^25.1.0",
"koa": "^2.11.0",
"koa-router": "^8.0.8",
"less": "^3.11.1",
"less-loader": "^5.0.0",
"lint-staged": "^10.0.8",
"mini-css-extract-plugin": "^0.9.0",
"mockjs": "^1.1.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"ora": "^4.0.3",
"portfinder": "^1.0.25",
"postcss-aspect-ratio-mini": "^1.0.1",
"postcss-loader": "^3.0.0",
"postcss-px-to-viewport": "^1.1.1",
"postcss-write-svg": "^3.0.1",
"preload-webpack-plugin": "^2.3.0",
"prettier": "^1.19.1",
"style-loader": "^1.1.3",
"terser-webpack-plugin": "^2.3.5",
"ts-jest": "^25.2.1",
"typescript": "^3.8.3",
"url-loader": "^3.0.0",
"webpack": "^4.42.0",
"webpack-bundle-analyzer": "^3.6.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3",
"webpack-merge": "^4.2.2"
},
dependencies:代表的是生产环境用的依赖 –save (对应的安装依赖的后缀)
devDependencies:代表的是开发环境相关的依赖 –save -dev (对应的开发环境安装文件的后缀)
3.2 文件夹作用和命名初步确认
(1)项目根目录
- config
打包配置
- public
静态文件夹
- index.html
- favicon.ico
- src
源码目录
(2)初始化命令
$ git init
$ npm init
初始化后生成初始化文件
3.3 打包文件文件命名及作用
- config
打包配置
- webpack.base.js //公共配置文件
- webpack.dev.js //开发环境文件
- webpack.prod.js //生产环境配置文件
- build.js //执行构建的相关命令文件
- config.js //用于提取可能会变动的环境配置文件
3.4 打包相关配置文件配置
webpack.base.js
const path = require('path');
const webpack = require('webpack');
const config = require('./config');
const APP_PATH = path.resolve(__dirname, '../src');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin }=require('clean-webpack-plugin');
module.exports = {
entry: { //入口
app: './src/index.tsx',
},
output: { //输出文件
filename: 'js/[name].bundle.js',
path: config.assetsRoot,
publicPath: config.publicPath
},
module: { //loader
rules: [
{
oneOf: [
{
test: /\.(html)$/,
loader: 'html-loader'
},
{
test: /\.(j|t)sx?$/,
include: APP_PATH,
use: [
{
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-react', // jsx支持
['@babel/preset-env', { useBuiltIns: 'usage', corejs: 2 }] // 按需使用polyfill
],
plugins: [
['@babel/plugin-proposal-class-properties', { 'loose': true }] // class中的箭头函数中的this指向组件
],
cacheDirectory: true // 加快编译速度
}
},
{
loader: 'awesome-typescript-loader'
}
]
},
{
test: /\.svg$/,
use: ['@svgr/webpack']
},
{
test: /\.(jpg|jpeg|bmp|png|webp|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024, // 小于这个大小的图片,会自动base64编码后插入到代码中
name: 'img/[name].[hash:8].[ext]',
outputPath: config.assetsDirectory,
publicPath: config.assetsRoot
}
},
{//加载处理文件
exclude: [/\.(js|mjs|ts|tsx|less|css|jsx)$/, /\.html$/, /\.json$/],
loader: 'file-loader',
options: {
name: 'media/[path][name].[hash:8].[ext]',
outputPath: config.assetsDirectory,
publicPath: config.assetsRoot
}
}
]
}
]
},
resolve: { //加载时
extensions: ['.js', '.json', '.jsx', '.ts', '.tsx'], // 自动判断后缀名,引入时可以不带后缀
alias: {
'@': path.resolve(__dirname, '../src/') // 以 @ 表示src目录
}
},
plugins: [
new HtmlWebpackPlugin({
inject: true,
template: config.indexPath,
showErrors: true
}),
new CleanWebpackPlugin(),//用于自动删除上次打包文件
],
optimization: {}
};
webpack.dev.js
const merge = require('webpack-merge');
const baseWebpackConfig = require('./webpack.base');
const config = require('./config');
module.exports = merge.smart(baseWebpackConfig, {
mode: 'development',
output: {
filename: 'js/[name].[hash:8].js',
publicPath: config.publicPath // 这里可以省略
},
module: {
rules: [
{
oneOf: []
}
]
},
})
webpack.prod.js
const config = require('./config');
const merge = require('webpack-merge');
const baseWebpackConfig = require('./webpack.base');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const sourceMapsMode = config.productionJsSourceMap ? 'source-map' : 'none';
module.exports = merge.smart(baseWebpackConfig, {
mode: 'production',
devtool: sourceMapsMode,//将编译后的代码映射回原始源代码。可以更准确的定位到错误代码的出处
output: {
filename: 'js/[name].[contenthash:8].js', // contenthash:只有模块的内容改变,才会改变hash值
},
plugins: [
new CleanWebpackPlugin(),
]
})
config.js//抽出部分可能会变化的属性 写入配置文件中,防止变更配置时需要同步修改生产环境和开发环境配置
const path = require('path');
module.exports = {
assetsRoot: path.resolve(__dirname, '../dist'),
assetsDirectory: 'static',
publicPath: '/',
indexPath: path.resolve(__dirname, '../public/index.html'),
productionJsSourceMap: false,
devServer: {
port: 8080,
host: 'localhost',
contentBase: path.join(__dirname, '../public'),
watchContentBase: true,
publicPath: '/',
compress: true,
historyApiFallback: true,
hot: true,
clientLogLevel: 'error',
open: true,
overlay: false,
quiet: false,
noInfo: false,
watchOptions: {
ignored: /node_modules/
},
proxy: {}
}
};
3.5 插件相关配置文件
根目录下创建相关文件,并写入配置
.commitlintrc.js//用于规范git commit命令时候的规范
module.exports = {
parserPreset: {
parserOpts: {
headerPattern: /^(\w*)(?:\((.*)\))?:\s(.*)$/,
headerCorrespondence: ['type', 'scope', 'subject']
}
},
rules: {
'type-empty': [2, 'never'],
'type-case': [2, 'always', 'lower-case'],
'subject-empty': [2, 'never'],
'type-enum': [
2,
'always',
['feat', 'fix', 'update', 'docs', 'style', 'refactor', 'test', 'chore', 'release', 'revert']
]
}
}
tsconfig.json//Typescript相关配置文件
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": [
"src"
],
"exclude": [
"./node_modules"
]
}
3.6 开始打包文件配置
build.js
const ora = require('ora');
const chalk = require('chalk');
const webpack = require('webpack');
const webpackConfig = require('./webpack.prod');
const spinner = ora('webpack编译开始...\n').start();
webpack(webpackConfig, function (err, stats) {
if (err) {
spinner.fail('编译失败');
console.log(err);
return;
}
spinner.succeed('编译结束!\n');
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n');
});
package.json中添加相应的脚本指令
"scripts": {
"build": "cross-env NODE_ENV=production node config/build.js",
},
四、测试打包情况和打包后的文件
npm run build 后
生成打包文件 /dist
五.现有配置实际实现的需求和待实现的需求
(1) 支持react和typescript,支持打包图片文件和代码相关文件。 生产环境下打包存在缓存(contenthash),只有当文件有变更时,才会更新对应的文件。公共配置提取。代码git命令规范化。css文件可以自动插入被打包的文件中
(2)待实现的需求:未支持生产环境打包自动压缩,未支持开发环境下热更新。编写代码格式未规范。