从基础到实战 手把手带你掌握新版Webpack4.0

Webpack

本篇博客由慕课网视频[从基础到实战手把手带你掌握新版Webpack4.0](https://coding.imooc.com/class/316.html)阅读整理而来,观看视频请支持正版。
本篇博客 Webpack 版本是4.0+,请确保你安装了Node.js最新版本。

Webpack是什么?

核心定义

webpack的核心定义是一个模块打包工具。javascript

官方文档查阅

https://webpack.js.org/concepts/css

GUIDES: 解决某一个方向问题答案,如代码分割,TypeScripthtml

CONCEPTS: 一些核心的概念vue

CONFIGURATION: 某个配置项java

API: 写一些loader,plugin插件node

LOADERS: 查看loader的做用,配置。(若是找不到到插件的githup上找)react

PLUGINS: 插件的做用,配置。(若是找不到到插件的githup上找)jquery

搭建Webpack环境

首先要安装node.js以及npm, 并保证是最新版本。(最新版本会加快打包构建速度)webpack

安装指令

全局安装ios

npm install webpack webpack-cli -g

项目安装

npm install webpack webpack-cli -D

带有版本的安装

npm install webpack@4.16.5 webpack-cli -D

卸载指令

npm uninstall webpack webpack-cli -g

查看当前Webpack版本

全局安装查看指令:

webpack -v

项目内安装查看指令:

npx webpack  -v

查看历史Webpack版本

npm info webpack

使用Webpack配置文件

建立配置文件

在根目录建立webpack.config.js

const path = require('path');//引入node核心模块

module.exports = {
    //mode: 'production',//默认模式,会压缩代码,不写便是默认,不写会有提示
    mode: 'development',//开发模式, 不会压缩代码
    entry: {
       main: './src/index.js'
    },//从哪个文件开始打包
    output: {//输出到哪里
        filename: 'bundle.js',//输出的文件名称
        path: path.resolve(__dirname,'bundle') //输出到哪个文件夹下, path后面跟绝对路径
        //__dirname指的是webpack.config.js文件当前所在的路径
    }
}

修改默认配置文件名字

把默认的webpack.config.js, 修改成webpackconfig.js

npx webpack --config webpackconfig.js

npx会在目录下的node_modules下面找webpack,没有就安装一个。npm 则先去全局,而后再去当前目录下的node_modules找webpack,没有就不找了

webpack执行命令

  • 全局(global)
webpack index.js
  • 局部(local)
npx webpack index.js
  • 修改后脚本执行命令(npm scripts
npm run bundle

package.json文件

{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "bundle":"webpack"
  },
  "author": "LiuJunFeng",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10"
  }
}

loader

loader是一个打包方案,它知道对于某一个特定的文件,webpack该如何进行打包。自己webpack是不知道对于一些文件(jpg,txt,excel)该如何处理的,可是loader知道。 因此webpack去求助loader就能够啦。

打包静态资源(图片篇)

安装插件

打包图片资源能够选用两个loader, 一个是file-loader,一个是url-loader

npm i file-loader -D
npm i url-loader -D

url-loader更加友好, 它能够经过图片大小来判断是使用base64格式图片仍是直接打包成一个图片资源文件。

配置文件

webpack.config.js

module: {
        rules: [{
            test: /\.(jpg|png|gif)$/,
            use: {
                // loader: 'file-loader',// 遇到jpg格式不知道怎么打包就去求助file-loader插件  
                loader: 'url-loader',//图片转化为base64, 不是单独生成一个文件。 
                options: {
                    // placeholder 占位符
                    name: '[name]_[hash].[ext]',//name 打包文件名字   name/原有文件名字  hash/本次打包哈希值  ext/原有名字后缀
                    outputPath: 'images/',//把图片文件打包到images目录下
                    limit: 204800//若是文件超过204800字节,就会像file-loader打包到dist目录下生成一个文件, 
                    //若是文件小于204800字节,那就回变成base64字符串, 放到js内
                }
            }
        }]
    }

原理

File-loader底层处理逻辑,先将文件转移到打包目录下,再将dist中的文件路径返回给index.js。

任何的静态文件均可以使用file-loader插件, 只要你但愿把静态文件移动到打包目录下而且获取到此文件地址。

打包静态资源(样式篇)

安装插件

css文件打包

打包css文件须要使用两个loaderstyle-loadercss-loader

npm i style-loader css-loader -D

css-loader帮咱们分析出几个css文件的引入关系, 最终将这些css文件合并成一段css。

style-loader 再获得css-loader生成的内容后, 会把这段代码挂载到html的head部分。

在打包css文件时, 必定要css-loaderstyle-loader配合使用。

sass文件打包
npm install sass-loader node-sass --save-dev
自动加厂商前缀
npm i -D postcss-loader
npm i autoprefixer -D

loader打包的执行顺序是从下到上(从右到左)来执行, 以下:sass文件会先执行sass-loader, 处理完后再执行style-loader挂载到html的head部分。

配置

webpack.config.js

在module对象内的rule数组内添加如下代码:

css样式文件配置

{
            test: /\.scss$/,
            use:[
                'style-loader',
                {
                  loader: 'css-loader',
                  options: {
                        importLoaders: 2,//在scss又引入另一个scss时,有可能直接走css-loader,不走sass-loader和postcss-loader,加上此配置项可让它继续走下面两个loader
                        //modules: true//开启css模块化打包  解决全局样式冲突
                    }
                },
                'sass-loader',//sass文件编译
                'postcss-loader'//加厂商前缀
            ]
        }]

字体文件配置

{
            test: /\.(eot|ttf|woff|woff2|svg)$/,
            use: {
                loader: 'file-loader',
                options:{
                    outputPath: 'fonts/',
                }
                
            }
        }

若是须要加厂商前缀, 须要在根目录在建立一个文件, 取名为postcss.config.js, 如下为具体配置:

module.exports = {
    plugins: [
        require('autoprefixer')
    ]
}

默认打包支持的浏览器,不须要厂商前缀,能够把浏览器条件放宽:

能够在根目录package.json文件中添加浏览器条件:

"browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ]

使用plugins

plugin 能够在webpack运行到某个时刻的时候, 帮你作一些事情。 很像vue中的生命周期函数。

生成html文件

安装

npm i -D html-webpack-plugin

html-webpack-plugin会在打包结束的时刻, 自动生成一个html文件, 并把打包生成的js自动注入到这个html文件中。

配置

首先须要引入该插件, 而后再module对象内写入plugins属性名, 属性是一个数组,数组内实例化该插件。

const HtmlWebpackPlugin = require('html-webpack-plugin');

plugins:[new HtmlWebpackPlugin()],

打包时清空dist目录

安装

npm i clean-webpack-plugin -D

clean-webpack-plugin 插件会在打包流程执行前清空dist目录

配置

const {CleanWebpackPlugin} = require('clean-webpack-plugin');
plugins:[new HtmlWebpackPlugin({
        template: 'src/index.html'
    }),new CleanWebpackPlugin()],

基础配置

Entry

entry: {
       main: './src/index.js',
       sub:'./src/index.js'
    },//从哪个文件开始打包

若是一个文件打包两次, 能够用以上方式配置, 一个文件会成为main.js, 一个会成为sub.js。

Output

output: {//输出到哪里
        publicPath:'http://cdn.com.cn',//文件使用cdn域名
        filename: '[name].js',//输出的文件名称  name对应的是entry里的key值
		chunkFilename:'[name].chunk.js',//间接引入的文件会加上.chunk
        path: path.resolve(__dirname,'dist') //输出到哪个文件夹下, path后面跟绝对路径
        //__dirname指的是webpack.config.js文件当前所在的路径
    }

SourceMap

可使用这个配置查看源代码那里有错误, 而不是打包后的文件错误。

好比你的js文件里面写的有问题,好比dist目录下的main.js文件第96行出错。SourceMap他是一个映射关系, 他知道dist目录下的main.js文件96行实际上对应的是src目录下index.js文件中的第一行。他就知道是index.js第一行出错了。

配置

module.exports = {
    //mode: 'production',//默认模式,会压缩代码,不写便是默认,不写会有提示
    mode: 'development',//开发模式, 不会压缩代码
    devtool:'source-map',
    entry: {
       main: './src/index.js'
    },//从哪个文件开始打包

devtool:'inline-source-map'

source-map会在dist目录下生成一个main.js.map, 而使用 inline-source-map会直接经过data-url的方式直接写在main.js内的底部。

devtool:'cheap-inline-source-map'

当咱们遇到代码量很大的时候, 若是咱们的代码出了错误, 加上cheap它就会只告诉咱们是哪一行出了错误, 而不会告诉咱们第几列。 打包会更加节省性能。

devtool:'cheap-module-inline-source-map'

source-map只会告诉咱们业务相关的代码是否有错, 而不会告诉咱们引入第三方模块代码是否有错误, 好比 loader内的错误。若是你想让它也管第三方模块的错误能够加上module。

devtool:'eval',

使用eval配置执行效率最快, 性能最好的打包方式。它使用了eval的语法执行源代码。可是若是比较复杂的代码状况下, 它提示出来的内容可能不太准确。

最佳实践

若是你在开发环境中, 使用source-map建议使用devtool:'cheap-module-inline-eval-source-map', 它提示出来的错误是比较全的, 同时它的打包速度也是比较快的。

若是在线上环境中, 不必使用source-map做为映射, 直接删除此配置便可。
固然若是你也须要看错误提示, 可使用devtool:'cheap-module-source-map'。它的提示会更好一些。

WebpackDevServer

修改源码自动就会进行打包。提高开发效率。

总共有如下两种方式:

webpack --watch

监听文件改动实时进行打包

package.json

"scripts": {
    "watch": "webpack --watch"
  },

devServer

自动打包并刷新浏览器,还能够模拟服务器上的特性。 vue以及react脚手架使用的都是此配置。推荐使用, 这个也是业界最常用的方案。

安装插件

npm i webpack-dev-server -D

配置

webpack.config.js

devServer: {
        contentBase: './dist',//服务器起在哪个文件夹下
        open: true,//自动打开浏览器
        port: 8080,//使用哪一个端口号
        proxy: {
            './api':'http://locallhost:3000'
        }//若是访问api这个地址,也就是locallhost:8000/api, 它会帮你转发到http://locallhost:3000这个地址上
    },

package.json

"scripts": {
    "watch": "webpack --watch",
    "start": "webpack-dev-server"
  },

热模块更新(HMR)

HMR是Hot Module Replacement的缩写。

它能够只更新你改动的文件, 不会直接刷新浏览器。

优势:

  1. 很是方便调试样式。

配置

webpack.config.js

const webpack = require('webpack');
devServer: {
        contentBase: './dist',//服务器起在哪个文件夹下
        open: true,//自动打开浏览器
        port: 8080,//使用哪一个端口号
        // proxy: {
        //     './api':'http://locallhost:3000'
        // }//若是访问api这个地址,也就是locallhost:8000/api, 它会帮你转发到http://locallhost:3000这个地址上
        hot:true,//开启热更新
        hotOnly:true//即使热更新没有生效,也不刷新浏览器
    },
plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html"
    }),
    new CleanWebpackPlugin(),
    new webpack.HotModuleReplacementPlugin()
  ],

index.js内

enter description here

若是开启热更新, number.js文件只要发生变化就会从新执行一下

使用Babel处理ES6语法

业务代码

安装插件

npm i -D babel-loader @babel/core

npm i @babel/preset-env -D
 
npm i --save @babel/polyfill

babel-loader插件只是做为babel与webpack沟通的桥梁, 若是想要翻译ES6语法, 须要安装@babel/preset-env插件。

babel/polyfill 用来补充babel/preset-env的, 有的语法babel/preset-env不能翻译(如Promise), 这时候可使用babel/polyfill。

配置

在业务代码js的顶部引入babel/polyfill 。若是使用了useBuiltIns:"usage",也能够不引入此插件。

import "@babel/polyfill";

webpack.config.js

{
        test: /\.js$/,//js文件由ES6转成ES5
        exclude: /node_modules/,//无论这个文件夹下的js文件
        loader: "babel-loader",
        options: {
            presets: [["@babel/preset-env",{
              targets: {
                edge: "17",
                firefox: "60",
                chrome: "67",
                safari: "11.1"
              },//浏览器版本,如chrome版本大于67将不会翻译成ES5
              useBuiltIns:"usage"//js里用哪一个翻译那个,不用的语法特性不会翻译, 减小文件体积
            }]]
        }
      }

打包组件库

若是开发组件库或者第三方模块的时候, 不要使用@babel/polyfill插件。由于它在注入promise或者map方法的时候, 它会经过全局变量的方式注入, 会污染到全局环境。

安装插件

咱们能够换一种方式:

首先把业务代码中引入的@babel/polyfill注释掉, 而后按照 @babel/plugin-transform-runtime和@babel/runtime插件。

npm i -D @babel/plugin-transform-runtime
 npm i --save @babel/runtime
  npm i --save @babel/runtime-corejs2

配置

webpack.config.js

{
        test: /\.js$/,//js文件由ES6转成ES5
        exclude: /node_modules/,//无论这个文件夹下的js文件
        loader: "babel-loader",
        options: {
            plugins: [["@babel/plugin-transform-runtime",{
              "corejs":2,
              "helpers":true,
              "regenerator":true,
              "useESModules":false
            }]]
        }
      }

若是plugins内corejs配置了2, 那么就要安装@babel/runtime-corejs2这个插件了。

固然, 在咱们配置过多的babel配置时, 也能够在根目录建立一个.babelrc文件。用来放置相关配置, 以下:

webpack.config.js

{
        test: /\.js$/,//js文件由ES6转成ES5
        exclude: /node_modules/,//无论这个文件夹下的js文件
		//include: path.resolve(__dirname,'../src')//只对src目录的js文件打包
        loader: "babel-loader"
      }

babelrc

{
    "plugins": [["@babel/plugin-transform-runtime",{
      "corejs":2,
      "helpers":true,
      "regenerator":true,
      "useESModules":false
    }]]
}

配置React代码打包

安装插件

npm i react react-dom --save
 npm i --save-dev @babel/preset-react

配置

babelrl

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "edge": "17",
          "firefox": "60",
          "chrome": "67",
          "safari": "11.1"
        },
        "useBuiltIns": "usage"
      }
    ],
    "@babel/preset-react"
  ]
}
这个执行顺序是按照从下到上,从右到左来执行的。顺序必定不要写反了。它是先执行react转成js, 而后执行babel转成ES5的。

Tree Shaking

Tree Shaking支持ES6的Module引入方式,它只支持静态引入的方式, 动态引入的方式它不支持。

Tree Shaking中文翻译是摇树的意思, 大意就是把无效的代码摇晃掉, 只留下有用的代码。
好比你引入了一个模块中的方法, 它就只打包你引入的方法, 不须要的方法不会进行打包了。

在开发环境中, 是没有Tree Shaking功能的, 若是须要请添加如下配置:

webpack.config.js

在线上环境中能够不配置这个

optimization:{
    usedExports:true
  },
  output: {
    //输出到哪里
    filename: "[name].js", //输出的文件名称  name对应的是entry里的key值
    path: path.resolve(__dirname, "dist") //输出到哪个文件夹下, path后面跟绝对路径
    //__dirname指的是webpack.config.js文件当前所在的路径
  }

package.json文件内

"name": "webpack-demo",
  "sideEffects": ["@babel/polly-fill"],
  "version": "1.0.0",
  "description": "",
  "private": true,

配置"sideEffects": ["@babel/polly-fill"],后, Tree Shaking不会对这个插件有任何做用了。

固然没有引用@babel/polly-fill也能够设置为false

若是你的业务js代码引入了js,以下:

enter description here

你也须要在"sideEffects"进行配置, 通常咱们会对css文件进行如下配置:

"sideEffects": [
    "*.css"
  ]

只要遇到任何的css文件,那么也不要去使用Tree Shaking。

Develoment 和 Production 模式的区分打包

在开发环境能够方便咱们开发, 有热模块更新DevServer等配置能够更加方便咱们的调试代码。
而线上环境会压缩代码, 对source-map精简(没有报错信息或者只显示行错误)。

开发环境

首先咱们须要把webpack.config.js修改成webpack.dev.js, 表示开发环境。

const path = require("path"); //引入node核心模块
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const webpack = require("webpack");

module.exports = {
  //mode: 'production',//默认模式,会压缩代码,不写便是默认,不写会有提示
  mode: "development", //开发模式, 不会压缩代码
  devtool: "cheap-module-eval-source-map",
  entry: {
    main: "./src/index.js"
  }, //从哪个文件开始打包
  devServer: {
    contentBase: "./dist", //服务器起在哪个文件夹下
    open: true, //自动打开浏览器
    port: 8080, //使用哪一个端口号
    // proxy: {
    //     './api':'http://locallhost:3000'
    // }//若是访问api这个地址,也就是locallhost:8000/api, 它会帮你转发到http://locallhost:3000这个地址上
    hot: true, //开启热更新
    hotOnly: true //即使热更新没有生效,也不刷新浏览器
  },
  module: {
    rules: [
      {
        test: /\.(jpg|png|gif)$/,
        use: {
          // loader: 'file-loader',// 遇到jpg格式不知道怎么打包就去求助file-loader插件
          loader: "url-loader", //图片转化为base64, 不是单独生成一个文件。
          options: {
            // placeholder 占位符
            name: "[name]_[hash].[ext]", //name 打包文件名字   name/原有文件名字  hash/本次打包哈希值  ext/原有名字后缀
            outputPath: "images/", //把图片文件打包到images目录下
            limit: 204800 //若是文件超过204800字节,就会像file-loader打包到dist目录下生成一个文件,
            //若是文件小于204800字节,那就回变成base64字符串, 放到js内
          }
        }
      },
      {
        test: /\.(eot|ttf|woff|woff2|svg)$/,
        use: {
          loader: "file-loader",
          options: {
            outputPath: "fonts/"
          }
        }
      },
      {
        test: /\.scss$/,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              importLoaders: 2 //在scss又引入另一个scss时,有可能直接走css-loader,不走sass-loader和postcss-loader,加上此配置项可让它继续走下面两个loader
              //modules: true//开启css模块化打包  解决全局样式冲突
            }
          },
          "sass-loader", //sass文件编译
          "postcss-loader" //加厂商前缀
        ]
      },
      {
        test: /\.css$/,
        use: [
          "style-loader",
          "css-loader",
          "postcss-loader" //加厂商前缀
        ]
      },
      {
        test: /\.js$/,//js文件由ES6转成ES5
        exclude: /node_modules/,//无论这个文件夹下的js文件
        loader: "babel-loader"
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html"
    }),
    new CleanWebpackPlugin(),
    new webpack.HotModuleReplacementPlugin()
  ],
  optimization:{
    usedExports:true
  },
  output: {
    //输出到哪里
    filename: "[name].js", //输出的文件名称  name对应的是entry里的key值
    path: path.resolve(__dirname, "dist") //输出到哪个文件夹下, path后面跟绝对路径
    //__dirname指的是webpack.config.js文件当前所在的路径
  }
};

启动命令

package.json

"scripts": {
    "dev": "webpack-dev-server --config wepack.dev.js"
  },

在开发环境内也能够把hotOnly: true去掉, 使浏览器能自动刷新。

线上环境

而后建立webpack.prod.js文件, 表示线上环境。

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const webpack = require("webpack");

module.exports = {
  mode: "production",
  devtool: "cheap-module-source-map",
  entry: {
    main: "./src/index.js"
  },
  module: {
    rules: [
      {
        test: /\.(jpg|png|gif)$/,
        use: {
          loader: "url-loader",
          options: {
            name: "[name]_[hash].[ext]",
            outputPath: "images/",
            limit: 204800
          }
        }
      },
      {
        test: /\.(eot|ttf|woff|woff2|svg)$/,
        use: {
          loader: "file-loader",
          options: {
            outputPath: "fonts/"
          }
        }
      },
      {
        test: /\.scss$/,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              importLoaders: 2
            }
          },
          "sass-loader",
          "postcss-loader"
        ]
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader", "postcss-loader"]
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader"
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html"
    }),
    new CleanWebpackPlugin()
  ],
  output: {
    filename: "[name].js",
    path: path.resolve(__dirname, "dist")
  }
};

启动命令

package.json

"scripts": {
    "dev": "webpack-dev-server --config wepack.dev.js",
    "build":"webpack --config webpack.prod.js"
  },

打包完成后把dist文件夹丢给后端使用便可。

公共文件

咱们能够发如今webpack.dev.js和webpack.prod.js中有不少相同代码, 这时候咱们能够把相同的代码抽离出来放到webpack.common.js中。

咱们须要引入一个插件合并common文件与prod或者dev文件

npm i webpack-merge -D

建立webpack.common.js, 相同代码放入此文件

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

module.exports = {
  entry: {
    main: "./src/index.js"
  },
  module: {
    rules: [
      {
        test: /\.(jpg|png|gif)$/,
        use: {
          loader: "url-loader",
          options: {
            name: "[name]_[hash].[ext]",
            outputPath: "images/",
            limit: 204800
          }
        }
      },
      {
        test: /\.(eot|ttf|woff|woff2|svg)$/,
        use: {
          loader: "file-loader",
          options: {
            outputPath: "fonts/"
          }
        }
      },
      {
        test: /\.scss$/,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              importLoaders: 2
            }
          },
          "sass-loader",
          "postcss-loader"
        ]
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader", "postcss-loader"]
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader"
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html"
    }),
    new CleanWebpackPlugin()
  ],
  output: {
    filename: "[name].js",
    path: path.resolve(__dirname, "dist")
  }
};

webpack.dev.js

const webpack = require("webpack");
const merge = require("webpack-merge");
const commonConfig = require("./webpack.common.js");

const devConfig = (module.exports = {
  mode: "development",
  devtool: "cheap-module-eval-source-map",
  devServer: {
    contentBase: "./dist",
    open: true,
    port: 8080,
    hot: true,
    hotOnly: true
  },
  plugins: [new webpack.HotModuleReplacementPlugin()],
  optimization: {
    usedExports: true
  }
});

module.exports = merge(commonConfig, devConfig);

webpack.prod.js

const merge = require("webpack-merge");
const commonConfig = require("./webpack.common.js");

const prodConfig = {
  mode: "production",
  devtool: "cheap-module-source-map"
};

module.exports = merge(commonConfig, prodConfig);

固然, 若是咱们这几个配置文件放在了根目录的build文件夹内, 你须要在package.json内对指令进行更改,把目录须要更改到build目录下

package.json

"scripts": {
    "dev": "webpack-dev-server --config ./build/webpack.dev.js",
    "build": "webpack --config ./build/webpack.prod.js"
  },

固然, 输出的目录也须要修改一下

webpack.common.js

output: {
    filename: "[name].js",
    path: path.resolve(__dirname, "../dist")
  }

Webpack 和 Code Splitting

Code Splitting指的是代码分割。若是咱们不使用代码分割,打包出来的文件会很大, 加载时间会很长。还有一种状况, 咱们引入一个lodash库, 这部分代码不变, 仅仅是业务代码改变了, 若是咱们再次打包就会所有又加载一遍, 影响了加载的速度。 咱们但愿的是lodash库不须要再次加载。

咱们先安装一个插件lodash

npm i lodash --save

src目录建立一个lodash.js文件

import _ from 'lodash';
window._ = _;

webpack.common.js

entry: {
    lodash: "./src/lodash.js",
    main: "./src/index.js"
  },

自动进行代码分割

同步代码方式

同步代码

以上为同步引入方式,可按照一下代码进行配置:

webpack.common.js

optimization: {
    splitChunks: {
      chunks: 'all'
    }
  },

这段代码能帮你作代码分割。

异步代码

异步引入指的是如下状况:

异步代码

安装插件
npm i babel-plugin-dynamic-import-webpack

.babelrc

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "edge": "17",
          "firefox": "60",
          "chrome": "67",
          "safari": "11.1"
        },
        "useBuiltIns": "usage"
      }
    ],
    "@babel/preset-react"
  ],
  "plugins": ["dynamic-import-webpack"]
}

小结

代码分割,和webpack无关

webpack中实现代码分割,两种方式

  1. 同步代码: 只须要在webpack.common.js中作optimization的配置便可
  2. 异步代码(import): 异步代码,无需作任何配置,会自动进行代码分割,放置到新的文件中

SplitChunksPlugin 配置参数详解

魔法注释

魔法注释

须要在package.json去掉babel-plugin-dynamic-import-webpack插件,由于它是一个第三方的插件,不支持魔法注释, 咱们须要一个官方的插件来进行魔法注释。

npm i -D @babel/plugin-syntax-dynamic-import

.babelrc

plugins修改成babel/plugin-syntax-dynamic-import

"plugins": ["@babel/plugin-syntax-dynamic-import"]

webpack.common.js

若是optimization不写任何内容, 只是一个空对象, 会按照如下默认配置打包:

optimization: {
    splitChunks: {
      chunks: 'aysnc',
      minSize: 30000,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name:true,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: false
      }
    }
  }

通常按照这个选项配置便可:

webpack.common.js

optimization: {
    splitChunks: {
      chunks: 'all',
    }
  }

参数说明

chunks

chunks: 'async'

默认配置是async

只对异步引入进行代码分割

异步引入

chunks: 'initial'

只对同步引入进行代码分割

同步引入

chunks: 'all'

所有都会进行代码分割,同时必需要配置defaultVendors

defaultVendors: {
 test: /[\\/]node_modules[\\/]/,
 priority: -10
}

执行过程是首先看是否须要代码分割, 也就是chunks配置,若是须要分割会走到cacheGroups内看如何分割, 从defaultVendors看看是否在node_modules里, 那它就符合这个配置的要求, 因而他就会把须要打包的模块(如lodash)打包在一个Vendors组里面去。

enter description here

这个文件是在vendors组内, 入口文件是main.js

vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          filename: 'vendors.js'
        },

加上filename可使文件名改成vendors.js

minSize

minSize: 30000

引入的文件大于30000字节才进行分割, 通常配置30000。

若是不是node_modules内的文件, 须要配置一个默认选项:

cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          filename: 'vendors.js'
        },
        default: {
          priority: -20,
          reuseExistingChunk: true,
          filename: 'common.js'
        }
      }

这样打包后会在文件前加default~的前缀, 以下:

enter description here

maxSize

基本上没用, 通常不会配置这个选项。

这个配置是进行二次拆分的选项。 好比一个lodash库, 你配置50000, 也就是50kb, 这个lodash库是1m, 那么它会拆分红20个文件, 可是通常状况下lodash库是拆分不了的。

minChunks

当一个模块引入了多少次才会进行代码分割。

maxAsyncRequests

同时加载的模块数量。通常不会配置。默认便可。

好比咱们这个选项配置为5, 加入我引入了10个库, 分割了10个文件,那你一打开网页的时候, 同时要加载10个文件。那就违反了maxAsyncRequests配置为5的要求, 同时只能加载5个请求, 那么webpack在打包前5个库的时候会为你生成5个js文件, 超过5个它就不会作代码分割了。

maxInitialRequests

通常不会配置。默认便可。

整个网站首页加载的时候, 或者说入口文件加载的时候, 入口文件可能会引入其它的js文件。入口文件引入的库若是是配置为3, 那么最多作3次代码分割。 超过3个就不会再作代码分割了。

automaticNameDelimiter

组和文件名作链接时的链接符

name

true会让cacheGroups起的名字有效。也就是filename

cacheGroups

cacheGroups也就是缓存组, 与上面的选项息息相关。会把符合条件的代码缓存到一个组内。

priority指的是优先级。 哪一个大优先哪一个。

reuseExistingChunk: true

假如我有一个a模块, 又有一个b模块。a模块内又使用了b模块。在打包a代码的时候, 因为a模块使用了
b模块,因此b模块代码也会被打包进去。可是若是配置这个选项,它会去看, 以前b模块代码已经被引入过,那么它会去复用以前打包的模块。

Chunks是什么?

webpack打包过程当中生成的每一个文件都是一个chunk

意义

代码分割配置

minChunks: 2 至少两个打包文件引入这个模块 才单独分割打包

Lazy Loading 懒加载

如下代码能够实现懒加载, 在点击页面后再加载代码

async function getComponent() {
	const { default: _ } = await import(/* webpackChunkName:"lodash" */ 'lodash');
	const element = document.createElement('div');
	element.innerHTML = _.join(['Dell', 'Lee'], '-');
	return element;
}

document.addEventListener('click', () =>{
	getComponent().then(element => {
		document.body.appendChild(element);
	});
})

优势: 页面加载速度更快。

懒加载并非webpack的功能, 它是ESModule的一个概念, 只不过webpack可以识别对它进行代码分割。

打包分析

https://github.com/webpack/analyse

package.json

"scripts": {
    "dev-build": "webpack --profile --json > stats.json --config ./build/webpack.dev.js",
    "dev": "webpack-dev-server --config ./build/webpack.dev.js",
    "build": "webpack --config ./build/webpack.prod.js"
  },

整个打包过程的描述放在stats.json文件内。

打开http://webpack.github.io/analyse/,把文件上传便可获得如下的分析图。

分析图

固然也可使用Webpack Bundle Analyzer这个插件。

Preloading, Prefetching

webpack 指望首次加载速度最优化,不是利用缓存在下次加载时提升访问速度 应该提升代码使用率

show coverage 代码使用率

交互代码能够放到单独的异步模块里 提升加载速度及页面利用率

以下:

业务代码

异步代码模块

可是异步加载交互代码时:例如当点击的时候才再加载异步代码,虽然提升了页面初始化速度,可是对用用户点击
的体验很差,速度太慢;

为了解决懒加载带来的问题:使用prefretch preload

prefetch:会等主流程都加载完成,等待空闲再加载;(最优)

import(/* webpackPrefetch: true */ 'LoginModal');

preload:是和主线程一块儿加载

css代码分割

想要使用css代码分割咱们必需要修改一下 Tree Shaking 配置

package.json

"sideEffects": [
    "*.css",
    "*.scss"
  ],

首先, 咱们须要安装一个插件

npm install --save-dev mini-css-extract-plugin

引入css文件

import './style.css';

webpack.prod.js配置

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  plugins: [new MiniCssExtractPlugin({
        filename: "[name].css",
     	chunkFilename: "[name].chunk.css"
	})],
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
};

压缩css代码:

npm i optimize-css-assets-webpack-plugin -D

webpack.prod.js

const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');

  optimization: {
    minimizer: [new OptimizeCSSAssetsPlugin({})]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
      chunkFilename: "[name].chunk.css"
    })
  ]
高级用法

多个入口文件引入的css文件打包到一块儿, 须要借助splitChunks,额外增长一个style组,只要发现你的打包文件是css文件, 统一打包到一个叫styles.css的文件内,enforcetrue忽略默认的一些参数(如minsize之类)。只要你是一个css文件我就作代码的拆分,把代码分割到style.css文件内。

webpack.prod.js

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  optimization: {
    splitChunks: {
      cacheGroups: {
        styles: {
          name: 'styles',
          test: /\.css$/,
          chunks: 'all',
          enforce: true,
        },
      },
    },
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css',
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
};

当咱们想要每一个入口文件打包到不一样的css文件内的时候,仍是利用cacheGroups, 以下: 若是入口文件是foo文件就走fooStyles的逻辑,若是是bar文件就走barStyles的逻辑。

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

function recursiveIssuer(m) {
  if (m.issuer) {
    return recursiveIssuer(m.issuer);
  } else if (m.name) {
    return m.name;
  } else {
    return false;
  }
}

module.exports = {
  entry: {
    foo: path.resolve(__dirname, 'src/foo'),
    bar: path.resolve(__dirname, 'src/bar'),
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        fooStyles: {
          name: 'foo',
          test: (m, c, entry = 'foo') =>
            m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry,
          chunks: 'all',
          enforce: true,
        },
        barStyles: {
          name: 'bar',
          test: (m, c, entry = 'bar') =>
            m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry,
          chunks: 'all',
          enforce: true,
        },
      },
    },
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css',
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
};

Webpack 与浏览器缓存

webpack.common.js

关闭性能上的警告

performance: false,

在咱们使用webpack的时候,线上代码修改的时候,由于代码的名字没有改变致使浏览器在加载
网页的时候,取的是缓存中的代码,致使没有及时的获取最新的代码,这时候就要清除浏览器的缓存, 咱们能够利用输出文件配置contenthash, 这样只有在修改代码了才会改变hash值, 就能够作到修改了代码线上浏览器缓存也会更新。

webpack.prod.js

output: {
 filename: '[name].[contenthash].js',
 chunkFilename: '[name].[contenthash].js'
 }

老版本若是发现即便没修改代码,打包文件的hash值也不同,请按下图配置:

enter description here

Shimming 的做用

shimming做用:解决webpack打包的兼容性问题.

好比你引入一个jquery.ui的库, 可是没有引入jquery, 使用了$写代码。 可是在你的业务是有引入jquery的, 这样在业务代码若是运行jqueryui初始化会报错的。 因此咱们应该使用shimming。

webpack.common.js

const webpack = require("webpack");

webpack.common.js

plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html"
    }),
    new CleanWebpackPlugin(),
    new webpack.ProvidePlugin({
      $: 'jquery',
	  _join:['lodash','join'],
	  //_: 'lodash'
    })
  ],

以上代码的意思就是若是个人一个模块中使用了$, 那我就会在模块里自动帮你引入jquery这个模块。
使用_join就是lodash下的join方法

模块内this指向window

每一个模块的this都是指向自身模块, 不会指向window。 若是想要指向window, 能够引入这个插件:

npm i imports-loader --save-dev

webpack.common.js

{
        test: /\.js$/,
        exclude: /node_modules/,
        use: [{
          loader: "babel-loader",
        },{
          loader: "imports-loader?this=>window"
        }]
      }

环境变量

咱们换一种方式来启动不一样环境下的打包方式, 经过一个变量:

package.json

{
  "name": "webpack-demo",
  "sideEffects": [
    "*.css",
    "*.scss"
  ],
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "dev-build": "webpack --profile --json > stats.json --config ./build/webpack.common.js",
    "dev": "webpack-dev-server --config ./build/webpack.common.js",
    "build": "webpack --env.production --config ./build/webpack.common.js"
  },
  "author": "LiuJunFeng",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.8.4",
    "@babel/plugin-syntax-dynamic-import": "^7.8.3",
    "@babel/plugin-transform-runtime": "^7.8.3",
    "@babel/preset-env": "^7.8.4",
    "@babel/preset-react": "^7.8.3",
    "autoprefixer": "^9.7.4",
    "babel-loader": "^8.0.6",
    "clean-webpack-plugin": "^3.0.0",
    "css-loader": "^3.4.2",
    "file-loader": "^5.0.2",
    "html-webpack-plugin": "^3.2.0",
    "imports-loader": "^0.8.0",
    "mini-css-extract-plugin": "^0.9.0",
    "node-sass": "^4.13.1",
    "optimize-css-assets-webpack-plugin": "^5.0.3",
    "postcss-loader": "^3.0.0",
    "sass-loader": "^8.0.2",
    "style-loader": "^1.1.3",
    "url-loader": "^3.0.0",
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10",
    "webpack-dev-server": "^3.10.3",
    "webpack-merge": "^4.2.2"
  },
  "dependencies": {
    "@babel/polyfill": "^7.8.3",
    "@babel/runtime": "^7.8.4",
    "@babel/runtime-corejs2": "^7.8.4",
    "jquery": "^3.4.1",
    "lodash": "^4.17.15",
    "react": "^16.12.0",
    "react-dom": "^16.12.0"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ]
}

webpack.common.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const webpack = require("webpack");
const merge = require("webpack-merge");
const devConfig = require("./webpack.dev.js");
const prodConfig = require("./webpack.prod.js")

const commonConfig = {
  entry: {
    main: "./src/index.js"
  },
  module: {
    rules: [
      {
        test: /\.(jpg|png|gif)$/,
        use: {
          loader: "url-loader",
          options: {
            name: "[name]_[hash].[ext]",
            outputPath: "images/",
            limit: 204800
          }
        }
      },
      {
        test: /\.(eot|ttf|woff|woff2|svg)$/,
        use: {
          loader: "file-loader",
          options: {
            outputPath: "fonts/"
          }
        }
      },
      {
        test: /\.scss$/,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              importLoaders: 2
            }
          },
          "sass-loader",
          "postcss-loader"
        ]
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [{
          loader: "babel-loader",
        },{
          loader: "imports-loader?this=>window"
        }]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html"
    }),
    new CleanWebpackPlugin(),
    new webpack.ProvidePlugin({
      $: 'jquery'
    })
  ],
  optimization: {
    usedExports: true,
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          name: 'vendors'
        }
      }
    }
  },
  performance: false,
  output: {
    path: path.resolve(__dirname, "../dist")
  }
};


module.exports = (env) => {
  if(env && env.production) {
    return merge(commonConfig, prodConfig)
  } else {
    return merge(commonConfig, devConfig)
  }
}

webpack.dev.js

const webpack = require("webpack");

const devConfig = (module.exports = {
  mode: "development",
  devtool: "cheap-module-eval-source-map",
  devServer: {
    contentBase: "./dist",
    open: true,
    port: 8080,
    hot: true,
    hotOnly: true
  },
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              importLoaders: 2
            }
          },
          "sass-loader",
          "postcss-loader"
        ]
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader", "postcss-loader"]
      }
    ]
  },
  plugins: [new webpack.HotModuleReplacementPlugin()],
  output: {
    filename: "[name].js",
    chunkFilename:'[name].js',
  }
});

module.exports =  devConfig;

webpack.prod.js

const webpack = require("webpack");

const devConfig = (module.exports = {
  mode: "development",
  devtool: "cheap-module-eval-source-map",
  devServer: {
    contentBase: "./dist",
    open: true,
    port: 8080,
    hot: true,
    hotOnly: true
  },
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              importLoaders: 2
            }
          },
          "sass-loader",
          "postcss-loader"
        ]
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader", "postcss-loader"]
      }
    ]
  },
  plugins: [new webpack.HotModuleReplacementPlugin()],
  output: {
    filename: "[name].js",
    chunkFilename:'[name].js',
  }
});

module.exports =  devConfig;

咱们也能够在package.json里这样写:

enter description here

那么对应的webpack.common.js是这样的

enter description here

还能够这么写:

package.json

enter description here

webpack.common.js

enter description here

打包组件库

建立一个新的文件夹Library, 并初始化项目:

npm init -y

安装webpack

npm i webpack webpack-cli --save

建立webpack.config.js配置如下代码:

const path = require('path');
module.exports = {
    mode: 'production',
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname,'dist'),
        filename: 'library.js',
        libraryTarget: 'umd',//不管什么方式引入组件均可以正确引入到
    }
}

package.json

{
  "name": "Library",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "LIU",
  "license": "MIT",
  "dependencies": {
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10"
  }
}

若是你想这样经过src引入js, 而且想经过library获取它下面的方法或属性,

enter description here

添加library: 'library'便可

webpack.config.js

const path = require('path');
module.exports = {
    mode: 'production',
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname,'dist'),
        filename: 'library.js',
        library: 'library',
        libraryTarget: 'umd',//不管什么方式引入组件均可以正确引入到
   		//libraryTarget: 'this',
		//libraryTarget: 'window',
		//libraryTarget: 'global',
   }
}

这样配置好如下几种方式均可以正确引入了:

enter description here

webpack.config.js

const path = require('path');
module.exports = {
    mode: 'production',
    entry: './src/index.js',
    externals: ["lodash"],//打包过程当中若是遇到lodash库你就忽略这个库,不要把它打包到你的代码中去,防止使用时用户重复引入库(例如:lodash)
    // externals: {
    //     lodash: {
    //         //root: '_',//全局script标签引入,必须在页面注入一个名字叫_的变量
    //         //commonjs: 'lodash'//若是个人lodash在commonjs这个环境中使用,而且引入名字必须是lodash
    //     }
    // },
    output: {
        path: path.resolve(__dirname,'dist'),
        filename: 'library.js',
        library: 'library',
        libraryTarget: 'umd',//不管什么方式引入组件均可以正确引入到
    }
}

commonjs

package.json

{
  "name": "Library",
  "version": "1.0.0",
  "description": "",
  "main": "./dist/library.js",
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "LIU",
  "license": "MIT",
  "dependencies": {
    "lodash": "^4.17.15",
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10"
  }
}

发布组件库流程:

  • 登陆npm
  • 命令行npm adduser,输入用户名,密码
  • 命令行 npm publish 发布到npm

PWA 的打包配置

安装插件模拟服务器

npm i http-server --save-dev

package.json

scripts命令

"start": "http-server dist",

PWA:是一种强缓存技术,访问过的页面就算服务器断开,也能经过缓存浏览以前访问的页面

只在上线环境配置就能够了, 开发环境不用考虑服务器挂掉不挂掉的问题。

安装插件

npm i workbox-webpack-plugin --save-dev

配置

webpack.prod.js

const WorkboxPlugin = require('workbox-webpack-plugin');
plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
      chunkFilename: "[name].chunk.css"
    }),
    new WorkboxPlugin.GenerateSW({
      clientsClaim: true,
      skipWaiting: true
    })
  ],

业务逻辑文件增长如下代码:

if ('serviceWorker' in navigator){
    window.addEventListener('load', ()=>{
        navigator.serviceWorker.register('/service-worker.js')
        .then(registration => {
            console.log('service-worker registed');
        }).catch(error => {
            console.log('service-worker register error');
        })
    })
}

TypeScript 的打包配置

安装插件:

npm i ts-loader typescript --save

webpack-config.js

module: {
        rule: [{
            test: /\.tsx?$/,
            use: 'ts-loader'
        }]
    },

建立tsconfig.json文件

{
    "compilerOptions": {
        "outDir": "./dist",//输出目录
        "module": "es6",//只容许es6 Module的方式引入模块
        "target":"es5",//编译为Es5这样的代码
        "allowJs":true//容许TS里引入js这样的模块
    }
}

若是想引入lodash库,而且想让它的非法错误提示出来, 首先安装一个这样的模块:

npm i @types/lodash --save-dev

以下图, 若是不向join方法传入参数会有报错提示

enter description here

若是还想安装jquery库, 那你也须要安装对应的类型文件

npm i @types/jquery --save-dev

这个网站能够查询都有哪些类型文件能够试用:

https://microsoft.github.io/TypeSearch/

WebpackDevServer

请求转发

若是咱们如今项目内发送请求, 咱们通常会安装一个axios库

npm i axios --save

enter description here

在项目中咱们通常会在开发环境有一个请求api以供测试, 线上环境有一个请求api。 这时候咱们通常须要使用相对路径写接口地址, 可是使用相对路径接口地址带上的就是localhost了, 这时候咱们须要作一个代理:

webpack.config.js

devServer: {
		contentBase: './dist',
		open: true,
		port: 8080,
		hot: true,
		hotOnly: true,
		proxy: {
			'/react/api': {
				target: 'https://www.dell-lee.com',
				secure: false,
				pathRewrite: {
					'header.json': 'demo.json'
				},
				changeOrigin: true,
				headers: {
					host: 'www.dell-lee.com',
				}
			}
		}
	},

secure: false: https的接口须要设置这个
pathRewrite: 至关于想要去获取header.json, 配置这个获取的是demo.json。 通常用于后端接口还没写好的时候使用一个demo数据, 等写好了再使用写好的接口,只须要把这个选项注释掉, 不用去业务代码中再修改了。

changeOrigin: true 始终配置就行, 主要为了有的网站使用了origin限制。

headers: 设置请求头, 可设置host, cookie

解决单页面应用路由问题

首先咱们须要安装一个路由插件:

npm i react-router-dom --save

在咱们使用单页应用时, 若是咱们要访问list页面, 那么服务器会觉得咱们访问的是一个叫list的页面。可是咱们的文件里并无一个list.html, 那它就会提示咱们页面不存在。

enter description here

enter description here

想要达到咱们预期的效果, 须要配置devServer

webpack.config.js

devServer: {
		contentBase: './dist',
		open: true,
		port: 8080,
		hot: true,
		hotOnly: true,
		historyApiFallback:true,

固然你也能够单独设置每一个地址的访问,如abc.html
转发到index.html

historyApiFallback: {
			rewrites: [{
				from: /abc.html/,
				to: '/index.html'
			}]
		},
historyApiFallback只在开发环境中有效,线上环境须要和后端配合

EsLint

使用eslint检测代码

安装插件

npm i eslint --save-dev
npx eslint --init

配置好文件之后能够用此命令查看项目文件是否符合语法:

npx eslint src

安装解析器

npm i babel-eslint --save-dev

.eslintrc.js

enter description here

vscode中的ESlint

其实咱们也能够不借助webpack, 直接使用编辑器自带的插件, 如vscode的Eslint插件, 这样使用会更加方便!

enter description here

若是咱们的团队有些规范并不想要符合airbnb它的规范, 咱们能够这么配置:
首先复制出规范的名称, 以下图:

enter description here

而后再.eslintrc.js文件的rules里进行配置:

enter description here

假设咱们团队有一个同窗使用的不是vscode, 他没有这样的语法提示, 就会跟咱们写的代码不同。 这时候咱们就须要借助webpack了:

首先咱们须要安装一个这样的插件:

npm i eslint-loader --save-dev

而后再webpack.config.js配置

enter description here

eslint-loader  必定要写在后面, 只有语法正确再进行转义或者其它。由于loader是先执行后边再执行前边的。
Eslint安装使用流程
  • 安装Eslint
  • 安装Eslint-loader
  • webpack内devServer配置overlay
  • webpack内js文件配置eslint-loader

Eslint其余配置

webpack.config.js

{
      test: /\.js$/,
      exclude: /node_modules/,
      use: ['babel-loader', {
        loader: 'eslint-loader',
        options: {
          fix: true,
        },
        force: 'pre',
      },
      ],
    }

fix: true 自动帮你修复比较浅显的问题
force: 'pre' 强制先执行eslint-loader

最佳实践

enter description here

不用配置webpack, 直接使用git的钩子, 再提交代码时验证语法。

webpack性能优化

1. 跟上技术的迭代(Node, Npm, Yarn)

2. 在尽量少的模块上应用Loader

能够干掉的配置:

enter description here

enter description here

3. Plugin尽量精简并确保可靠

4. resolve参数合理配置

enter description here

extensions 建议配逻辑文件, css,图片类不要配置, 浪费性能。

5. 使用DllPlugin 提升打包速度

咱们引入了一个lodash库, 咱们知道这个库的文件它是不会变的, 可是每次打包都会打包它, 咱们可让它只在第一次打包, 下次就不打包了。

首先建立一个 webpack.dll.js

const path = require('path');
const webpack = require('webpack');

module.exports = {
	mode: 'production',
	entry: {
		vendors: ['lodash'],
		react: ['react', 'react-dom']
	},
	output: {
		filename: '[name].dll.js',
		path: path.resolve(__dirname, '../dll'),
		library: '[name]'
	},
	plugins: [
		new webpack.DllPlugin({
			name: '[name]',
			path: path.resolve(__dirname, '../dll/[name].manifest.json'),
		})
	]
}

运行命令打包组件库:

enter description here

而后安装一个插件:

npm i  add-asset-html-webpack-plugin --save

在webpack.common.js引入

const fs = require('fs');//引入核心模块
const AddAssetHtmlPlugin = require("add-asset-html-webpack-plugin")
const plugins = [
	new HtmlWebpackPlugin({
		template: 'src/index.html'
	}), 
	new CleanWebpackPlugin(['dist'], {
		root: path.resolve(__dirname, '../')
	})
];

const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
files.forEach(file => {
	if(/.*\.dll.js/.test(file)) {
		plugins.push(new AddAssetHtmlWebpackPlugin({
			filepath: path.resolve(__dirname, '../dll', file)
		}))
	}
	if(/.*\.manifest.json/.test(file)) {
		plugins.push(new webpack.DllReferencePlugin({
			manifest: path.resolve(__dirname, '../dll', file)
		}))
	}
})

module.exports = {
	plugins
}

6. 控制包大小

不要引入无用组件库, 多使用Tree Shaking, 使用SplitChunks代码拆分。

7. thread-loader, parallel-webpack, happypack 多进程打包

8. 合理使用SourceMap

不要太详细,配置合适的便可

9. 结合Stats分析打包结果

10. 开发环境内存编译, 无用插件剔除

开发环境配置模式为development

mode: "development",

多页面打包配置

配置entry:

enter description here

想要添加多页面首先在src目录增长对应的js文件,而后在entry增长入口文件

webpack.common.js

const path = require("path");
const fs = require("fs");
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require("html-webpack-plugin");
const AddAssetHtmlWebpackPlugin = require("add-asset-html-webpack-plugin");
const webpack = require("webpack");

const makePlugins = configs => {
  const plugins = [new CleanWebpackPlugin()];

  Object.keys(configs.entry).forEach(item => {
    plugins.push(
      new HtmlWebpackPlugin({
        template: "src/index.html",
        filename: `${item}.html`,
        chunks: ["runtime", "vendors", item]
      })
    );
  });

  const files = fs.readdirSync(path.resolve(__dirname, "../dll"));
  files.forEach(file => {
    if (/.*\.dll.js/.test(file)) {
      plugins.push(
        new AddAssetHtmlWebpackPlugin({
          filepath: path.resolve(__dirname, "../dll", file)
        })
      );
    }
    if (/.*\.manifest.json/.test(file)) {
      plugins.push(
        new webpack.DllReferencePlugin({
          manifest: path.resolve(__dirname, "../dll", file)
        })
      );
    }
  });

  return plugins;
};

const configs = {
  entry: {
    main: "./src/index.js",
    list: "./src/list.js"
  },
  resolve: {
    extensions: [".js", ".jsx"]
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        include: path.resolve(__dirname, "../src"),
        use: [
          {
            loader: "babel-loader"
          }
        ]
      },
      {
        test: /\.(jpg|png|gif)$/,
        use: {
          loader: "url-loader",
          options: {
            name: "[name]_[hash].[ext]",
            outputPath: "images/",
            limit: 10240
          }
        }
      },
      {
        test: /\.(eot|ttf|svg)$/,
        use: {
          loader: "file-loader"
        }
      }
    ]
  },
  optimization: {
    runtimeChunk: {
      name: "runtime"
    },
    usedExports: true,
    splitChunks: {
      chunks: "all",
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          name: "vendors"
        }
      }
    }
  },
  performance: false,
  output: {
    path: path.resolve(__dirname, "../dist")
  }
};

configs.plugins = makePlugins(configs);

module.exports = configs;

如何编写一个loader

首先建立一个文件夹 make-loader, 对项目初始化, 而后安装webpack

npm init -y
npm i webpack webpack-cli --save-dev

建立文件夹 loader, loader文件夹内建立文件replaceLoader.js

咱们要实现的目标是若是发现咱们的业务逻辑文件内有dell这个字符, 咱们要把它修改为dellLee

//replaceLoader.js
//source 引入文件的内容
//this.query携带传过来的参数,name是loader配置的name参数
//loaderUtils能够分析参数, 好比参数是个对象的时候能够用到
//this.callback代替return 传递除result,还能够传递sourcemap等内容
//异步loader 使用this.async()
const loaderUtils = require('loader-utils');
module.exports = function (source) {
    const options = loaderUtils.getOptions(this);
    const callback = this.async();
    
    setTimeout(() => {
        const result = source.replace('dell',options.name);
        callback(null,result)
    }, 1000);
}
//replaceLoader.js
module.exports = function (source) {
    return source.replace('dell','world');
}

webpack.config.js

const path = require('path');
module.exports = {
    mode: "development",
    entry: {
        main: './src/index.js'
    },
    //当你引入loader的时候,会先在node_modules里找,若是没有回去loader里去找
    resolveLoader: {
        modules: ['node_modules','./loaders']
    },
    module: {
        rules: [{
            test: /\.js/,
            use: [{
                loader: 'replaceLoader',
            },{
                loader: 'replaceLoaderAsync',
                options: {
                    name: 'lee'
                }
            }]
        }]
    },
    output: {
        path: path.resolve(__dirname,'dist'),
        filename: '[name].js'
    }
}

若是你的loader配置参数有些诡异, 如是一个对象, 这时候咱们可使用一个插件作分析:

npm i loader-utils --save-dev

如何编写一个Plugin

Plugin与loader的区别

loader

当咱们在源代码中引入一个新的js文件,或者一个其余格式的文件的时候, 咱们可使用loader处理这个引入的文件。

Plugin

在咱们作打包的时候, 在某一个具体时刻上。 好比说, 当我打包结束后,我要自动生成一个html文件, 这时候咱们就可使用一个html-webpack-plugin的插件。它会在打包结束后生成html文件。
Plugin能够在咱们打包过程的某个时刻想作一些事情。

首先咱们初始化一个项目, 安装webpack和webpack-cli。
若是咱们想要生成带版权的文件, 能够这么作:

建立文件夹plugins, plugins文件夹内建立copyright-webpack-plugin.js

//options是plugin配置传过来的参数
//compiler是webpack的实例,存储了咱们webpack相关各类各样的配置文件,打包过程,等等一系列的内容。
//钩子, 指某个时刻会自动执行的函数。 如vue生命周期
//emit  当你把打包资源放到目标文件夹的时刻。它是一个异步的钩子。 能够在后面写一个tabAsync. 他有两个参数 第一个是插件名字,第二个是剪头函数
//compile  同步的钩子, 后面跟tap, 箭头函数只传compilation
//compilation 和compiler不同, 只存放此次打包的相关内容
//compilation.assets 打包生成的内容
class CopyrightWebpackPlugin {
    // constructor(options) {
    // }
    apply(compiler) {
        compiler.hooks.compile.tap('CopyrightWebpackPlugin',(compilation)=>{
            console.log('compiler');
        })
        compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin',(compilation,cb) => {
            compilation.assets['copyright.txt'] = {
                source: function() {
                    return 'copyright by dell lee'
                },
                size: function() {
                    return 21;
                }
            };
            cb();
        })
    }
}

module.exports = CopyrightWebpackPlugin

在webpack.config.js配置:

const path = require('path');
const CopyRightWebpackPlugin = require('./plugins/copyright-webpack-plugin')
module.exports = {
    mode: 'development',
    entry: {
        main: './src/index.js'
    },
    plugins: [
        new CopyRightWebpackPlugin()
    ],
    output: {
        path: path.resolve(__dirname,'dist'),
        filename:'[name].js'
    }
}

node调试工具

enter description here

--inspect 开启node调试工具
--inspect-brk webpack执行命令的第一行打断点

输入 npm run debug后打开浏览器, 点击控制台左上角node小图标

enter description here

这时候咱们就能够看到插件的详细信息了

可在watch增长compilation的监控

enter description here

Bundler 源码编写

模块分析

Vue CLI 3 的配置方法

Vue 内的Webpack设计理念是让咱们用的更爽, 即便是webpack小白用户咱们也可以轻松使用。

若是咱们想配置webpack, 须要在项目的根目录建立一个vue.config.js
这里的配置和webpack并不同, 它对webpack的配置进行了大量的封装, 若是咱们须要配置, 可参考脚手架的配置参考:https://cli.vuejs.org/zh/config/

若是咱们想实现原生的webpack, 在脚手架参考文档使用configureWebpack 便可。

相关文章
相关标签/搜索