1.安装好node,新建一个目录,进入该目录然后执行
//创建文件夹
mkdir webpack4demo
//进入
cd webpack4demo
//初始化
npm init -y
会在该目录下出现一个package.json文件
2.局部安装webpack,webpack-cli
npm install webpack,webpack-cli —-save-dev
3.npm script 运行webpack
模块的局部安装会在node_modules/.bin目录创建软链接,因此webpack打包时需要手动在终端输入 ./node_modules/.bin/webpack 这时才能运行webpack打包
为了方便使用可以在package.json中设置
"scripts":{
"build":"webpack"
}
通过 npm run build 指令执行打包
devDependencies:用于本地开发环境。存放如构建,eslint,单元测试等相关依赖
dependencies:用于用户发布环境,开发阶段的依赖最后不会被打入包内。存放如框架,组件,utils等业务逻辑相关的包
4.webpack核心概念——entry
entry:指定打包入口
单入口:entry是一个字符串
module.exports={
entry:'./path/filename.js' //入口文件,若不配置webpack4将自动查找src目录下的index.js文件
}
多入口:entry是一个对象
module.exports={
entry: {
app: './path/app.js',
admin: './path/admin.js'
},
}
5.output:如何将编译后的文件输出到磁盘
const path = require('path');
module.exports={
entry:'./path/filename.js',
output: {
filename: '[name].[chunkhash:8].js',//输出文件名,[name]表示入口文件js名
path: path.join(__dirname, 'dist')//输出文件路径
},
}
6.loaders:
webpack只支持js和json两种文件类型通过loaders去支持其他文件类型并将他们转化为有效的模块可以添加到依赖图中
Loaders 本身是一个函数,接受源文件作为参数,返回转换的结果
名称 | 描述 |
---|---|
babel-loader | 转换es6,es7等js新特性语法 |
css-loader | 支持.css文件的加载和解析 |
Less-loader | 将less文件转化为css |
ts-loader | 将ts转化为js |
file-loader | 进行图片,字体等的打包 |
raw-loader | 将文件以字符串的形式导入 |
thread-loader | 多进程打包js,css |
module.exports={
module:{
rules:[
{
test/\.txt$/, //————>指定匹配规则
use:’raw-loader’ //use指定使用loader名称
}
]
}
}
7.plugins:插件
常见的plugins:
名称 | 描述 |
---|---|
CommonsChunkPlugin | 将chunks相同的模块代码提取成公共js |
CleanWebpackPlugin | 清理构建目录 |
ExtractTextWebpackPlugin | 将css从bundle文件里提取成一个独立的css |
CopyWebpackPlugin | 将文件或文件夹拷贝到构建的输出目录 |
HtmlWebpackPlugin | 创建html文件去承载输出的bundleUglifyjsWebpackPlugin压缩js |
ZipWebpackPlugin | 将打包出来的资源生成一个zip包 |
const path = require('path');
//html扩展包
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports={
plugins:[
new HtmlWebpackPlugin({ //———>放在plugins数组里
template:path.join(__dirname, 'src/admin.html'),
})
]
}
9.解析es6:使用babel-loader
先下载依赖包
npm i @babel/core @babel/preset-env babel-loader -D
module.exports={
module:{
rules:[
{
test/\.js$/, //————>指定匹配规则
use:'babel-loader' //use指定使用loader名称
}
]
}
}
babel的配置文件是.babelrc
{
"presets":[ //————>多个plugin 集合
"@babel/preset-env"
],
"plugins":[ //———>用来支持某个功能
"@babel/proposal-class-properties"
]
}
- babel-loader解析es6语法也需要通过.babelrc进行配置 知道哪些语法需要解析,
- @babel/preset-env 就是告诉 babel-loader 需要解析的es6语法
preset: babel 插件集合的预设,包含某些插件 plugin。显然像上面那样一个一个配置插件会非常的麻烦,为了方便,babel 为我们提供了一个配置项叫做 persets(预设)。
babel 模块
1.babel-core(核心)
babel 的核心 api 都在这个模块中。也就是这个模块会把我们写的 js 代码抽象成 AST 树;然后再将 plugins 转译好的内容解析为 js 代码。
2.babel-cli
babel-cli 是一个通过命令行对 js 文件进行转换的工具。
当然我们一般不会使用到这个模块,因为一般我们都不会手动去做这个工作,这个工作基本都集成到模块化管理工具中去了,比如 webpack、Rollup 等。
3.babel-node
babel-node 是 babel-cli 的一部分,所以它在安装 babel-cli 的时候也同时安装了。
它使 ES6+ 可以直接运行在 node 环境中。
4.babel-polyfill(内部集成了 core-js 和 regenerator)
babel只转换新的js语法,不转换新的API。因此需要babel-polyfill转换 es6 的 API 为 es5:如 Map,Set,Promise。
babel-polyfill 主要有两个缺点:
- 使用 babel-polyfill 会导致打出来的包非常大,很多其实没有用到,对资源来说是一种浪费。
- babel-polyfill 可能会污染全局变量,给很多类的原型链上都作了修改,这就有不可控的因素存在。
解决方法:
方法一: Babel6: babel-runtime & babel-plugin-transform-runtime
.babelrc 配置:
{
"presets":[
["env"]
],
"plugins":[
[
"transform-runtime",
{
"helpers":false, // defaults to true
"polyfill": false, // defaults to true
"regenerator": true, // defaults to true
"moduleName": "babel-runtime" // defaults to "babel-runtime"
}
]
]
}
我们在启用插件 babel-plugin-transform-runtime 后,Babel 就会使用 babel-runtime 下的工具函数,将一些浏览器不能支持的特性重写,然后在项目中使用。
由于采用了沙盒机制,这种做法不会污染全局变量,也不会去修改内建类的原型,所以会有重复引用的问题。
现在最好的实践应该是方法二,如下。
方法二:Babel7: 新增了 babel-preset-env,设置
"useBuiltIns":"usage"这个参数值就可以实现按需加载 babel-polyfill 。(具体如下)
useBuiltIns 配置
全局引入 babel-polyfill,这样打包后的整个文件体积必然是会变大,通过设置 "useBuiltIns":"usage" 能够把 babel-polyfill 中你需要用到的部分提取出来,不需要的去除。
useBuiltIns 参数说明:
- false: 不对 polyfills 做任何操作
- entry: 根据 target 中浏览器版本的支持,将 polyfills 拆分引入,仅引入有浏览器不支持的 polyfill
- usage(新):检测代码中 ES6/7/8 等的使用情况,仅仅加载代码中用到的 polyfills
.babelrc 文件
{
"plugins": [
"transform-es2015-template-literals", // 转译模版字符串的 plugins
],
"presets": [
[
"env", {
// 是否自动引入 polyfill,开启此选项必须保证已经安装了 babel-polyfill
// “usage” | “entry” | false, defaults to false.
"useBuiltIns": "usage"
}
],
"stage-2"
]
}
plugins 与 presets 同时存在的执行顺序
- 先执行 plugins 的配置项,再执行 presets 的配置项;
- plugins 配置项,按照声明顺序执行;
- presets 配置项,按照声明逆序执行。
eg:上面代码的执行顺序是
- transform-es2015-template-literals
- stage-2
- env
方案对比
方案 | 优点 | 缺点 |
---|---|---|
@babel/runtime & @babel/plugin-transform-runtime | 按需引入, 打包体积小 | 不能兼容实例方法 |
@babel/polyfill | 完整模拟 ES2015+ 环境 | 打包体积过大, 污染全局对象和内置的对象原型 |
@babel/preset-env | 按需引入, 可配置性高 | -- |
babel7更新
preset 的变更:
淘汰 es201x,删除 stage-x,推荐 env
如果你还在使用 es201x,官方建议使用 env 进行替换。淘汰并不是删除,只是不推荐使用。
但 stage-x 是直接被删了,也就是说在 babel7 中使用 es201X 是会报错的。
包名称变化
把所有 babel-* 重命名为 @babel/*,
例如:
- babel-cli —> @babel/cli。
- babel-preset-env —> @babel/preset-env
低版本 node 不再支持
babel 7.0 开始不再支持 nodejs 0.10, 0.12, 4, 5 这四个版本,相当于要求 nodejs >= 6。
还有一些包从其他包独立出来的变化等等
10.解析css,less,sass
先下载依赖包
//解析css
npm i style-loader css-loader -D
//解析less
npm i less less-loader -D
//解析sass
npm i sass sass-loader -D
module.exports={
module:{
rules:[
{
test/\.css$/,
use:[
'style-loader', //因为loader是链式从右向左调用,因此顺序千万不能错,先使用css-loader再使用style-loader
'css-loader']
},
{
test: /\.less$ /,
use: [
'style-loader', //因为loader是链式从右向左调用,因此顺序千万不能错,先使用css-loader再使用style-loader
'css-loader',
'less-loader']
},
{
test: /\.sass$ /,
use: [
'style-loader', //因为loader是链式从右向左调用,因此顺序千万不能错,先使用css-loader再使用style-loader
'css-loader',
'sass-loader']
},
]
}
}
11.解析图片和字体
先下载依赖包
npm i file-loader -D
module.exports={
module:{
rules:[
{
test: /\.(jpg|png|svg|gif)$/, //图片
use: [{
loader: 'file-loader',
options: {
outputPath:'images/',//输出到images文件夹
limit:500, //是把小于500B的文件打成Base64的格式,写入JS
name: '[name].[hash:7].[ext]',//ext为后缀,hash是md5生成的24位字符串,:7表示取前七位
}]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/, //字体
use: [{
loader: 'file-loader',
options: {
limit: 10000,
name: '[name].[hash:7].[ext]',
}]
}
]
}
}
url-loader也可以处理图片和字体,可以设置较小资源自动base64(base64的好处就是减少http请求次数,优化页面加载性能),其内部也用到了file-loader
12.文件监听
文件监听就是在发现源文件发生变化时自动重新构建出新的输出文件。
webpack 开启监听模式:(缺点:浏览器不会自动刷新,需要手动刷新浏览器)
- 启动webpack命令时加上 —watch 参数
- 在配置webpack.config.js中设置 watch:true
package.json
"scripts":{
"build":"webpack",
"watch":"webpack —-watch" //开启监听模式
}
13.热更新:webpack-dev-server
先下载依赖包
npm i webpack-dev-server -D
package.json
"scripts":{
"build":"webpack",
"watch":"webpack —-watch", //开启监听模式
"dev":"webpack-dev-server ——open" // ——open表示每次自动打开浏览器
}
webpack.config.js
module.exports={
devServer: { //配置开发服务功能
// 你要提供哪里的内容给虚拟服务器用。这里最好填绝对路径。
contentBase: path.resolve(__dirname, 'dist'),
// 服务器的IP地址,可以使用IP也可以使用localhost
host: 'localhost',
// 服务端压缩是否开启,目前开不开都行,想关你就关
compress: true,
// 配置服务端口号,建议别用80,很容易被占用,你要是非要用也是可以的。
port: 9090,
clientLogLevel: 'warning',
historyApiFallback: true, //如果为 true ,页面出错不会弹出 404 页面。
hot: true, //修改或模块后,保存会自动更新,页面不用刷新呈现最新的效果。它依赖于webpack.HotModuleReplacementPlugin插件
}
}
14.文件指纹策略:chunkhash,contenthash,hash
打包后输出文件名的后缀
便于版本管理,没有修改的文件可以缓存,加速页面加载速度
选项 | 描述 |
---|---|
hash | 和整个项目的构建有关,只要项目文件有修改,整个项目构建的hash值就会更改 |
chunkhash | 和webpack打包的chunk有关,不同的entry会生成不同的chunkhash值 |
contenthash | 根据内容定义hash,文件内容不变则contenthash不变 |
注意点:
- js里没有contenthash
- bundle: 打包最终生成的文件
- chunk: 每个chunk是多个module组成,可以通过代码分割成多个chunk
- module: webpack中的模块(js,css,图片等等)
- 在开发环境中chunkhash与contenthash 会和热更新插件冲突,因此在开发环境中不使用chunkhash后缀名或者把devser里的hot:true注释掉,但是开发环境中hot还是很有必要的,因此推荐开发环境中不使用chunkhash后缀名,只在生产环境中使用。
15.压缩css,js,html
先下载依赖包
npm i mini-css-extract-plugin optimize-css-assets-webpack-plugin cssnano -D
webpack.config.js
const path = require('path');
//html扩展包
const HtmlWebpackPlugin = require('html-webpack-plugin');
//从js中提取css到单独的文件
const MinicssExtractPlugin = require('mini-css-extract-plugin');
//压缩css
const OtimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports={
mode:'development', //——>指定当前构建环境
entry:[
app:'./path/app.js',
admin:'./path/admin.js'
],
output:{
filename:'[name].js',
path:path.join(__dirname,'dist');
},
module:{
rules:[
{
test: /\.css$/,
use: [MinicssExtractPlugin.loader, 'css-loader'] //此处用MinicssExtractPlugin.loader替代'style-loader'
}
]
},
plugins:[
new HtmlWebpackPlugin({ //———>放在plugins数组里
template:path.join(__dirname,'src/admin.html'),
filename:'admin.html',
chunks:['admin'], //我的理解是一个入口就是一个chunk
inject:true, //是否注入css,js,true表示在打包完成的 admin.html 内自动引入 chunks 为 admin 的css,js
minify:{ //压缩html
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
minifyCss:true, //——>此处的minifyCss/js是为了压缩admin.html页面内的内嵌css/js,并不会对外链的css/js有影响
minifyJs:true
}
}),
// 提取并压缩css
new MinicssExtractPlugin({
filename: config.cssOutputPath
}),
// 压缩css
new OtimizeCssAssetsPlugin({
assetNameRegExp: /\.css$/g,
cssProcessor: require('cssnano') //此处压缩css代码配合cssnao
}),
]
}
16.自动清理构建目录
下载插件:
npm i clean-webpack-plugin -D
webpack.config.js
const path = require('path');
//自动清理构建目录
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports={
plugins:[
new CleanWebpackPlugin()
]
}
17.postcss autoprefixer插件自动补齐css3前缀
下载插件
npm i postcss-loader autoprefixer -D
方法一:
webpack.config.js
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: [
MinicssExtractPlugin.loader, //此处用MinicssExtractPlugin.loader替代'style-loader'
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: () => [ //一定要注意这里是[ ]
require('autoprefixer')({
overrideBrowserslist: ['last 5 version', '>1%', 'ios 7']
})
]
}
}
]
},
]
}
}
方法二:
添加一个.browserslistrc配置文件
last 5 version
> 1%
ios 7
webpack.config.js
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: [
MinicssExtractPlugin.loader, //此处用MinicssExtractPlugin.loader替代'style-loader'
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: [require('autoprefixer')]
}
}
]
},
]
}
}
方法三:
在package.json中增加如下配置
{
"browserslist":[
"last 5 version",
"> 1%",
"ios 7"
]
}
webpack.config.js
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: [
MinicssExtractPlugin.loader, //此处用MinicssExtractPlugin.loader替代'style-loader'
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: [require('autoprefixer')]
}
}
]
},
]
}
}
18.移动端px自动转rem
下载px2rem-loader
npm i px2rem-loader -D
页面渲染时计算根元素的font-size使用手机淘宝的lib-flexible
npm i lib-flexible -S
并将其内联在网页内(raw-loader下节会讲解)
webpack.config.js
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: [
MinicssExtractPlugin.loader, //此处用MinicssExtractPlugin.loader替代'style-loader'
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: [require('autoprefixer')]
}
},
{
loader:'px2rem-loader',
options:{
remUnit:75,
remPrecision:8
}
}
]
},
]
}
}
19.静态资源内联—raw-loader
css,js,图片等静态资源内联到html内,css内联避免页面闪动
下载raw-loader(raw-loader返回的是一个字符串)
npm i [email protected] -D
内联js:
html页面(raw-loader!babel-loader!后面是js文件路径)
内联css:
方案一:借助style-loader
webpack.config.js
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: [
{
loader:'css-loader',
options:{
insertAt:'top',//——>样式插入到head
singleton:true //——>将所有的style标签合并成一个
}
},
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: [require('autoprefixer')]
}
}
]
},
]
}
}
方案二:html-inline-css-webpack-plugin
npm i html-inline-css-webpack-plugin -D
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HTMLInlineCSSWebpackPlugin = require("html-inline-css-webpack-plugin").default;
module.exports = {
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css"
}),
new HtmlWebpackPlugin(),
new HTMLInlineCSSWebpackPlugin(),
],
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader"
]
}
]
}
}
20.多页面打包通用方案
动态获取 entry 和设置 html-webpack-plugin 数量
利用glob.sync
npm i glob -D
//引入glob
const glob=require('glob');
const path = require('path');
//引入html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin');
//动态设置entry,htmlwebpackplugin
const setMPA=()=>{
const entry={};
const HtmlWebpackPlugins=[];
const entryFiles=glob.sync(path.join(__dirname,'./src/*/index.js'));
Object.keys(entryFiles).map((index)=>{
const entryFile=entryFiles[index];
const match=entryFile.match(/src\/(.*)\/index\.js/);
const pageName=match&&match[1];
entry[pageName]=entryFile;
HtmlWebpackPlugins.push(
new HtmlWebpackPlugin({
filename: path.join(__dirname, `dist/${pageName}.html`),//输出文件名
template: path.join(__dirname,`src/${pageName}/index.html`),
chunks:[pageName],//我的理解是一个入口就是一个chunk
inject: true,//是否注入css,js,true表示在打包完成的 [pageName].html 内自动引入 chunks 为 [pageName] 的css,js
minify: { //压缩html
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
minifyCss: true, //——>此处的minifyCss/js是为了压缩[pageName].html页面内的内嵌css/js,并不会对外链的css/js有影响
minifyJs: true
},
// 通过CommonsChunkPlugin持续处理多个块的必要条件
chunksSortMode: 'dependency',
})
)
})
return {entry,HtmlWebpackPlugins}
}
const {entry,HtmlWebpackPlugins} = setMPA();
module.exports = {
entry:entry,
plugins:[].concat(HtmlWebpackPlugins)//将HtmlWebpackPlugins添加到plugins的数组中
}
21.devtool:
一般来说:生产环境推荐使用none.
对于开发环境推荐使用cheap-module-source-map、eval-source-map
source-map,inline-source-map会暴露源码.
nosources-source-map会隐藏源码
22.提取公共资源
基础库分离,减少代码冗余,提高加载速度
通过cdn导入,不打包在包内
利用SplitChunksPlugin进行公共脚本分离
他是webpack4内置的,替代CommonsChunkPlugin插件,CommonsChunkPlugin 已弃用
module.exports = {
optimization: {
splitChunks: {
chunks: 'async',
minSize: 30000, //——>最小尺寸必须大于此值,默认30000B
maxSize: 0,
minChunks: 1, //—>其他entry引用次数大于此值,默认1,个人理解minChunks指的是被不同entry引入的次数,为1时,适合分离 node_moudles 里的第三方库
maxAsyncRequests: 5, //——> 异步请求的chunks不应该超过此值
maxInitialRequests: 3, //—-->entry文件请求的chunks不应该超过此值(请求过多,耗时)
automaticNameDelimiter: '~', //——>自动命名连接符
name: true, //——>生成文件名
cacheGroups: { //——>它决定生成的文件
commons: { // 抽离自己写的公共代码
chunks: "initial",
name: "common", // 打包后的文件名,任意命名
minChunks: 2,//最小引用2次
minSize: 0 // 只要超出0字节就生成一个新包
},
vendors: { // 抽离第三方插件
test: /[\\/]node_modules[\\/]/, //限制范围,可以是正则,匹配出需要分离的包
name: 'vendor', // 打包后的文件名,任意命名
priority: 10 //优先级,多个分组冲突时决定把代码放在哪块,防止和自定义的公共代码提取时被覆盖,不进行打包
}
}
}
}
}
chunks 值为"initial", "async" 或 "all"
- initial 入口chunk,同步引入的库进行分离
- async 异步chunk,异步引入的库进行分离,忽略同步引入的库(默认)
- all: 所有引入的库进行分离(推荐)
23.tree shaking的使用和原理
摇树原理就是把有用的打包到bundle里去,把没用的在uglify阶段删除。摇树原理用到了DCE
DCE (Elimination) 包括以下几种形式:
- 代码不会执行,不可到达
- 代码执行结果不会被用到
- 代码只会影响死变量(只写不读)
生产环境下默认开启tree-shaking
css摇树,需要配合css提取插件使用
我自己尝试了一下感觉并不好用,引了bootstrap结合自己的css,结果只能获取其中一个样式,另一个样式被干掉了,目前没有找到解决方法,于是先放弃了css摇树
找到上面的解决方法就是通过glob-all传入多个参数,处理多路径文件
安装:
npm install -D purifycss-webpack purify-css glob-all
webpack.config.js
const glob = require('glob-all');
const purifycssWebpack = require('purifycss-webpack');
// Make sure this is after ExtractTextPlugin!
new purifycssWebpack({
paths: glob.sync([path.resolve('./src/*.html'),path.resolve('./src/*.js')])
}),
注:注意插件顺序:html文件生成 > css提取 > css摇树
24.scope hoisting使用和原理分析
模块转化分析:
- 被webpack转换后的模块会带上一层包裹
- import会转换成__webpack_require
scope hoisting原理:
将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突。
通过scope hoisting可以减少函数声明代码和内存开销
scope hoisting使用
Webpack4 mode设为production会默认开启scope hoisting;必须是es6语法,commonjs不支持
25.代码分割
使用场景
- 抽离相同代码到同一个共享块
- 脚本懒加载,使得初始下载的代码更小
懒加载js脚本
方式一:commonJS:require.ensure
方式二:ES6:动态import(目前还没有原生支持,需要babel转换)
下载插件:
npm install @babel/plugin-syntax-dynamic-import --save-dev
{
plugins:['@babel/plugin-syntax-dynamic-import'],
}
25.eslint
js代码检查工具,帮助发现代码错误规则,保持团队的代码风格统一
eslint-config-airbnb
如何执行落地?
和CI/CD系统集成
和webpack集成
使用eslint-loader,构建时检查js规范
26.webpack打包基础组件和基础库
library:指定库的全局变量
libraryTarget:支持库的引用方式
webpack.config.js
//正常压缩代码,mode为production的时候默认使用此插件压缩,但是此处mode为none,所以需要手动添加插件
const TerserPlugin=require('terser-webpack-plugin');
module.exports={
entry:{
'large-number':'./src/index.js',
'large-number.min':'./src/index.js'
},
output:{
filename:'[name].js',//包名
library:'largeNumber',//指定库的全局变量
libraryTarget:'umd',//支持该库的引用方式,ES module,CJS,AMD,直接通过 script 引⼊入
libraryExport:'default'//设置为default,这样引用时不需要再加default,否则引用时还需要使用largeNumber.default()
},
mode:'none',
optimization:{
minimize:true,
minimizer:[
// 压缩代码
new TerserPlugin({
include:/\.min\.js$/
})
]
}
package.json入口mian:index.js
//判断引入该包的项目处于生产还是开发环境,不同环境引入不同文件
if(process.env.NODE_ENV==='production'){
module.exports=require('./dist/large-number.min.js')
}else{
module.exports=require('./dist/large-number.js')
}
27.webpack实现SSR打包(本章学的很模糊)
服务端渲染(SSR)是什么
客户端渲染:HTML+CSS+JS+Data -->渲染后的HTML
服务端:
- 所有的模板等资源都存储在服务端
- 内网机器拉取数据更快
- 一个HTML返回所有数据
浏览器和服务器交互流程
客户端渲染 vs 服务端渲染
SSR 的优势
- 减少白屏时间
- 对于 SEO 友好
SSR 代码实现思路
服务端:
- 使⽤ react-dom/server 的 renderToString 方法将 React 组件渲染成字符串
- 服务端路由返回对应的模板
客户端: - 打包出针对服务端的组件
webpack ssr 打包存在的问题
1.浏览器器的全局变量量 (Node.js 中没有 document, window)
- 组件适配:将不兼容的组件根据打包环境进行适配
- 请求适配:将 fetch 或者 ajax 发送请求的写法改成 isomorphic-fetch 或者 axios
2.样式问题 (Node.js 无法解析 css)
- ⽅案⼀:服务端打包通过 ignore-loader 忽略掉 CSS 的解析
- ⽅案⼆:将 style-loader 替换成 isomorphic-style-loader
3.如何解决样式不显示的问题?
- 使⽤用打包出来的浏览器端 html 为模板
- 设置占位符,动态插入组件
4.⾸首屏数据如何处理?
- 服务端获取数据
- 替换占位符
28.当前构建时的日志显示
展示一⼤堆日志,很多并不需要开发者关注
统计信息 stats
如何优化命令⾏的构建日志
使⽤ friendly-errors-webpack-plugin 插件
- success: 构建成功的⽇志提示
- warning: 构建警告的日志提示
- error: 构建报错的日志提示
npm i friendly-errors-webpack-plugin
// 优化构建日志
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
module.exports={
plugins:[
new FriendlyErrorsWebpackPlugin()
],
// 统计信息 stats,仅显示错误信息
stats: 'errors-only',
}
如何判断构建成功
在 CI/CD 的 pipline 或者发布系统需要知道当前构建状态
每次构建完成后输⼊ echo $? 获取错误码
构建异常和中断处理
webpack4 之前的版本构建失败不会抛出错误码 (error code)
Node.js 中的 process.exit 规范
- 0 表示成功完成,回调函数中,err 为 null
- ⾮ 0 表示执行失败,回调函数中,err 不为 null,err.code 就是传给 exit 的数字
如何主动捕获并处理构建错误?
compiler 在每次构建结束后会触发 done 这 个 hook
process.exit 主动处理理构建报错
plugins: [
function() {
this.hooks.done.tap('done', (stats) => {
if (stats.compilation.errors && stats.compilation.errors.length && process.argv.indexOf('- -watch') == -1)
{
console.log('build error');
process.exit(1); }
})
}
]
29.webpack 构建配置
构建配置的可选方案
- 通过多个配置文件管理不同环境的构建
"dev": "webpack-dev-server --config webpack.dev.js", "build": "webpack --config webpack.pro.js"
- 将所有配置放在同一个文件,通过--env参数控制分支选择
"dev": "webpack-dev-server --env development ", "build": "webpack --env production"
- 将构建配置设计成一个库,比如:hjs-webpack、Neutrino、webpack-blocks
- 抽成一个工具进行管理,比如:create-react-app, kyt, nwb
构建配置包设计
- 通过多个配置文件管理不同环境
- 基础配置:webpack.base.js
- 开发环境:webpack.dev.js
- 生产环境:webpack.prod.js
- SSR环境:webpack.ssr.js
- 抽离成一个 npm 包统一管理
- 通过 webpack-merge 组合配置
const merge = require('webpack-merge'); const baseWebpackConfig = require('./webpack.base.conf'); module.exports = merge(baseWebpackConfig,devWebpackConfig);
功能模块设计
目录结构设计
- lib 放置源代码
- test 放置测试代码
|- /test
|- /lib
|- webpack.dev.js
|- webpack.prod.js
|- webpack.ssr.js
|- webpack.base.js
|- README.md
|- CHANGELOG.md
|- .eslinrc.js
|- package.json
|- index.js
我的个人博客,有空来坐坐