webpack 究竟是什么?

webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler),当 webpack 处理应用程序时,会递归构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将这些模块打包成一个或多个 bundle

传统模式开发

新建 index.html,如下:

<!DOCTYPE html>
<html lang="en">
<body>
  <p>这是网页内容</p>
  <div id="root"></div>
  <script src="./header.js"></script>
  <script src="./content.js"></script>
  <script src="./footer.js"></script>
  <script src="./index.js"></script>
</body>
</html>

再建立index.jsheader.jscontent.jsfooter.js

//index.js
new Header()
new Content()
new Footer()

//header.js
function Header () {
  var dom = document.getElementById('root')
  var header = document.createElement('div')
  header.innerText = 'header'
  dom.append(header)
}

//content.js
function Content () {
  var dom = document.getElementById('root')
  var content = document.createElement('div')
  content.innerText = 'content'
  dom.append(content)
}

//footer.js
function Footer () {
  var dom = document.getElementById('root')
  var footer = document.createElement('div')
  footer.innerText = 'footer'
  do
m.append(footer)
}

这样面向对象new出各个模块的构造函数,在一定程度上比面相过程全部写在一个文件上好维护,但是HeaderContet这些构造函数指向问题还要回到index.html文件一一对应查找,就造成了在index.js上指定不明确,如果我们能像以下代码一样导入就能解决这个问题了

//index.js
import Header from './header.js'
import Content from './content.js'
import Footer from './footer.js'

new Header()
new Content()
new Footer()

遗憾的是,浏览器不会识别这种es6的语法,使用浏览器访问index.html会发现控制台会报错。此时我们的webpack就可以为了解决这种问题而诞生了

使用webpack模块化开发

首先想使用webpack,我们得先npm初始化包并安装webpack

npm init
npm install webpack webpack-cli -D

想用ESmodule 语法导出,我们得修增加导出语法

//header.js
export default Header

//content.js
export default Content

//footer.js
export default Footer

然后我们就可以使用npx 运行 webpack 执行 index.js 文件

npx webpack index.js

执行后,我们发发现根目录下生成了一个 dist/main.js目录文件,这便是webpack默认出口输出(output),后面会介绍。这个文件便是webpack翻译我们的index.js生成后的文件,所以有的人会称webpackJS的翻译器,但这说法也并不完成正确。既然我们翻译了文件,我们就需要在index.html修改成导入后的文件,如下修改:

//index.html
<script src="./dist/main.js"></script>

修改完成后打开浏览器重新访问index.html发现可以生成相对于模块并且没有报错了,这就是webpack的模块开发。

使用webpack的配置文件(核心概念)

webpack 的核心概念

  • entry: 入口

  • output: 输出

  • loader: 模块转换器,用于把模块原内容按照需求转换成新内容

  • 插件(plugins): 扩展插件,在webpack构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要做的事情

使用一个配置文件

webpack v4 中,可以无须任何配置,因为webpack提供了默认配置,然而大多数项目会需要很复杂的设置,这就是为什么 webpack 仍然要支持 配置文件。这比在 terminal(终端) 中手动输入大量命令要高效的多,所以让我们在根目录创建一个配置文件:webpack.config.js(默认配置文件,可通过npx webpack --config webpack.config.js修改)

//webpack.config.js
const path = require('path')
module.exports = {
  entry: './src/index.jss' , //入口文件 默认:src/index.js
  output: { //出口文件 默认: dist/main.js
    filename: 'bundle.js', //输出的文件名 
    path: path.resolve(__dirname,'dist') //输出的路径,只能是绝对路径
  }
}

新建src文件夹,移动index.js,header.js等js到src文件夹,执行以下代码会发现dist文件夹生成了新的出口文件bundele.js

npx webpack src/index.js

entry

配置需要打包入口文件

//webpack.config.js
<!--单个文件-->
module.exports = {
  entry: './src/index.jss' , //入口文件 默认:src/index.js
}

<!--打包多个入口文件-->
module.exports = {
 entry: {
    main: './src/index.js', //入口文件 默认:src/index.js
    sub: './src/sub.js'
  },
}

output

配置打包输出的文件

  output: { //出口文件 默认: dist/main.js
    filename: 'bundle.js', //输出的文件名 
    path: path.resolve(__dirname,'dist') //输出的路径,只能是绝对路径
  }
  
  <!--多个入口文件需要不同名称文件输出配置-->
  output: { //出口文件 默认: dist/main.js
    filename: '[name].js', //输出的文件名 
    path: path.resolve(__dirname, 'dist') //输出的路径,只能是绝对路径
  },

npm scripts

考虑到用 CLI 这种方式来运行本地的 webpack 副本并不是特别方便,我们可以设置一个快捷方式。调整 package.json 文件,添加在 npm scripts 中添加一个 npm 命令:

//package.json
"scripts": {
    "bundle" : "webpack"
},

现在,可以使用 npm run build 命令,来替代我们之前使用的 npx 命令。注意,使用 npm scripts,我们可以像使用 npx 那样通过模块名引用本地安装的 npm packages。这是大多数基于 npm 的项目遵循的标准,因为它允许所有贡献者使用同一组通用脚本(如果必要,每个 flag 都带有 --config 标志)。

现在运行以下命令,然后看看你的脚本别名是否正常运行:

npm run bundle

浅析webpack打包输出内容

npm run bundle

> webpack4@1.0.0 bundle E:\project\webpack4
> webpack

Hash: 768c04b37ed214487576
Version: webpack 4.42.0
Time: 98ms
Built at: 2020-03-24 4:57:54 PM
    Asset      Size  Chunks             Chunk Names
bundle.js  1.29 KiB       0  [emitted]  main
Entrypoint main = bundle.js
[0] ./src/index.js + 3 modules 741 bytes {0} [built]
    | ./src/index.js 147 bytes [built]
    | ./src/header.js 202 bytes [built]
    | ./src/content.js 198 bytes [built]
    | ./src/footer.js 194 bytes [built]


WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

webpack打包输出的结果我们可以看出有个警告,那是因为我们没有指定打包输出的环境('development' or'production'),我们可以在webpack.config.js新增以下代码:

module.exports = {
  mode: 'development', //默认为production
  ...
}

重新执行npm run bundle 发现不会提出警告了,并且生成的bundle.js的代码没有被压缩

什么是 loader

webpack 可以使用 loader 来预处理文件。这允许你打包除 JavaScript 之外的任何静态资源。如果你在默认配置下打包除js文件外出错,所以我们要借助loader来打包js外的文件

现在我们在src文件下存放一张logo.jpg的图片,然后在index.js引入后使用webpack打开

//index.js
const logo = require('./logo.jpg')

执行npm run bundle打包后会抛出如下错误:

ERROR in ./src/logo.jpg 1:0
Module parse failed: Unexpected character '�' (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
(Source code omitted for this binary file)
 @ ./src/index.js 5:13-34

这是因为webpack像我们上面说的一样,只能打包js文件,如果想打包除js以外的静态资源,此时我们就需要借用loader来帮助我们打包图片资源。想打包图片我们首先得先安装相关的loader

file-loader

npm install file-loader -D

然后我们需要在webpack.config.js文件下配置相关的module规则:

//webpack.config.js
...
module: {
    rules: [
      {
        test: /\.jpg$/,
        use: [
          {
            loader: 'file-loader' //使用相对应的loader
          }
        ]
      }
    ]
  }

控制台npm run bundle再次运行时,发现打包成功没有报错,并且在dist出口目录生成了相应的图片资源。那我们导入的图片资源变量会是什么呢,我们试着打印一下:

const logo = require('./logo.jpg')
console.log('logo',logo)


可以在浏览器中看出,我们获取到的是打包生成的文件相关信息,包括图片名字。经过这个例子,我们就明白了在vue的脚手架项目中可以这样引入.vue相关文件的了

import Header from 'Header.vue'
//webpack.config.js
rules: [
    ...
      {
        test: /\.vue$/,
        use: { //只使用一个loader可以直接用对象配置
          loader: 'vue-loader'
        }
      }
    ]

上面案例中,我们可以看到options字段,这是我们可以打包文件资源的时候定义相对应的规则,比如:

rules: [
      {
        test: /\.jpg$/,
        use: [
          {
            loader: 'file-loader', //使用相对应的loader
            options: {
              name:'[name].[ext]', //定义打包生成的文件名字
              outputPath: 'images' //定义输出的文件目录:dist/images 下
            },
          }
        ]
      }
    ]

这样我们就可以自定义打包文件的名字和目录,更多的规则可以查看官方文档配置

url-loader

我们可以用url-loader取代file-loader来实现静态文件资源打包,那为什么我们有file-loader还要用url-loader,让我看看下面的例子就知道了:

首先我们先npm install url-loader --save-dev安装file-loader,然后在webpack.config.js进行相对应的配置

rules: [
      {
        test: /\.jpg$/,
        use: [
          {
            loader: 'url-loader', //使用相对应的loader
            options: {
              limit: 10240 //单位:字节 超过10kb 的文件生成图片,否则生成base64编码
            }
          }
        ]
      }
    ]

运行打包后我们可以发现,超过10kb 的文件生成图片,否则生成base64编码。这样做的好处是什么呢,图片生成base64编码可以大大减少我们https请求,但不是所有的图片都生成base64编码,比如图片几M的生成的话,相对应js文件大小也会增加,网页打空白的时间也相对应增加,至于哪些需要转,limit需要设置多大限制根据自己的项目来,我的项目中一般是icon图标类的会转base64编码,其他大的相对应打包生成文件。

使用loader打包样式静态资源

当我们尝试打包css文件的时候,如果没有使用相对应的样式loader就会打包失败。我们在src目录下创建新的文件index.csslogo.css,并且在index.js引入该样式文件:

//logo.css
.logo{
  width: 100px;
  height: 100px;
}
//index.css
@import './logo.css'
//index.js
import logo from './logo.jpg'
import './index.css'

var img = new Image()
img.src = logo
img.classList.add('logo')
var root = document.getElementById('root')
root.append(img)

打包之后可以看到控制台抛出了相对应的报错,此时我们应该接入css-loaderstyle-loader来解决这个问题,首先我们先得安装两个loader

npm install css-loader style-loader -D
   rules: [
    ...
      {
        test: /\.css$/,
        //注:打包执行顺利从右到左,从下到上,不能颠倒,先接css-loader转换语法在使用style-loader解析到头部标签
        use: ['style-loader','css-loader'] 
      }
    ]

css-loader

主要用于打包css文件中解析@import等语法,将 CSS 转化成 CommonJS 模块。

css-loader还可以配置更多的选项,比如importLoadersmodules等。如果没有配置importLoaders,在一个scss文件中@import其他的scss文件,可能该导入的scss文件不会生效css-loader后面配置的loader(sass-loader,postcss-loader)

use: ['style-loader',{
          loader: 'css-loader',
          options: {
            importLoaders: 2 // 0 => no loaders (default); 1 => postcss-loader; 2 => postcss-loader, sass-loader
          }
        },'sass-loader','postcss-loader'
    ]

配置modules参数为true可以模块化导入相关的样式文件,否则会全局样式污染。如下列案例:

//index.js
import logo from './logo.jpg'
import './index.scss'
import createLogo from './logo.js'

createLogo()
var img = new Image()
img.src = logo
img.classList.add('logo')
var root = document.getElementById('root')
root.append(img)
//logo.js
import logo from './logo.jpg'
function createLogo () {
  var img = new Image()
  img.src = logo
  img.classList.add('logo')
  var root = document.getElementById('root')
  root.append(img)
}
export default createLogo

上面因为没有配置相关的样式模块导入,所以导入index.scss文件的样式都在两张图片成功生效,下面我们增加下模块配置引入:

    //webpack.congig.js
      ...
        test: /\.scss$/,
        use: ['style-loader', {
          loader: 'css-loader',
          options: {
            importLoaders: 2, // 0 => no loaders (default); 1 => postcss-loader; 2 => postcss-loader, sass-loader
            modules: true //按模块化引入
          }
        }, 'sass-loader', 'postcss-loader'
        ]
      }
//index.js
import logo from './logo.jpg'
import style from './index.scss'
import createLogo from './logo.js'

createLogo()
var img = new Image()
img.src = logo
img.classList.add(style.logo)
var root = document.getElementById('root')
root.append(img)

重新打包后,我们发现只有index.js文件的图片生效了样式,我们模块化导入样式成功,更多的options配置样式可以查看官方文档

style-loader

配合css-loader使用,以形式在html页面中头部标签插入css代码。

sass-loader

npm install sass-loader node-sass webpack --save-dev

我们除了安装sass-lader外,并且还需要你预先安装 Node Sass。 这可以控制所有依赖的版本, 并选择要使用的 Sass 实现。新建src/index.sass

//index.sass
body {
  .logo{
    width: 100px;
    height: 100px;
  }
}
//index.js
import logo from './logo.jpg'
import './index.sass'

var img = new Image()
img.src = logo
img.classList.add('logo')
var root = document.getElementById('root')
root.append(img)

我们需要在webpack.config.js新增相对应的规则配置:

//webpack.config.js
 rules: [
    ...
      {
        test: /\.sass$/,
        use: ['style-loader','css-loader','sass-loader'] //先把sass转成css ,再进行起来的loader操作(右到左)
      }
    ]

配置后重新执行npm run bundle打包,在浏览器中可以正常访问,把sass-loader去掉再打包后,可以查看控制台头部样式中sass的语法没有转成css,这就是sass-loader的作用

postcss-loader

npm install postcss-loader -D

postcss-loader可以对css3样式前缀自动补全,兼容各个浏览器,使用postcss-loader前我们得配置相关的插件等,根目录下新建postcss.config.js,安装相对应的插件:autoprefixer(补全css3语法插件)

npm install autoprefixer -D
//postcss.config.js
module.exports = {
  plugins: [
    require('autoprefixer')
  ]
}
//webpack.config.js
rules: [
  ...
  {
    test: /\.scss$/,
    use: ['style-loader','css-loader','sass-loader','postcss-loader']
  }
]

autoprefixer补全得结合browserslist一起使用

//package.json
"browserslist": [
    "defaults",
    "ie >= 10",
    "last 2 versions",
    "> 1%",
    "iOS 7",
    "last 3 iOS versions"
 ],

使用plugins让打包更快捷

插件(plugins): 扩展插件,在webpack构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要做的事情,就类似vue生命周期钩子一样,在某种场景,帮你做某些事情。官方已收录的插件

HtmlWebpackPlugin

一种用于打包生成html的插件:HtmlWebpackPlugin会在打包结束后,自动生成html文件,并把打包生成的js自动引入到这个html文件中。具体配置可查看HtmlWebpackPlugin文档

//安装HtmlWebpackPlugin文档
npm install --save-dev html-webpack-plugin
//webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
    ...
    plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html' //生成的模板文件
    }),
    
  ]
}
//public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>html 模板</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>

CleanWebpackPlugin

有时候我们打包总是要手动删除掉上一次打包的文件,我们就想有没有什么工具能帮助我们在打包前自动删除掉dist目录,CleanWebpackPlugin就可以帮我们解决这个问题,详细配置

//webpack.config.js
  ...
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './public/index.html'
    }),
  ]

source-map

source-map 可以解决打包后代码报错的地方是打包后的代码而不是源业务代码的问题

//index.js
console.log('devtool',test)

这是未设置source-map报错的打包代码

我们在webpack.config.js 配置下

devtool: 'source-map'

配置后重新打包久可以看到报错的是原业务代码,但是我们不建议直接用source-map,我建议开发环境使用eval-cheap-module-source-map,生成环境用cheap-module-source-map,不同的配置打包的速度不一样,可以简单总结,source-map 会生成.map文件来映射,打包速度会很慢,因为还要映射打包文件,inline可以不生产.map文件,直接打包在出门文件里面转成和base64,cheap可以只报行除出错而不加列出错,module 可以让第三方loader 插件也生效报错,eval可以直接执行eval函数所以速度最快,具体可以参考官方文档配置

webpackDevServer

有时候我们修改了打包入口的文件,总是要重新打包编译打开浏览器访问,有没有一种配置能让我们监听到入口文件修改,就能自动打包编译在浏览器刷新呢,webpackDevServer就可以帮你做到,webpackDevServer会在本地帮你的项目搭建一个服务器来跑,只要你更新它就可以帮你重新打包编译~

首先我们得安装webpackDevServer

npm install webpack-dev-server -D

然后配置相关的参数

//webpack.config.js
module.exports = {
 ...
  devServer: {
    contentBase: './dist',
    port: 9000, //服务端口号
    open: true, //首次打包编译自动打开浏览器
    proxy: {//反向代理,一般用于解决跨域问题
      '/api': 'http://localhost:3000'
    }
  },

执行npm run start 就可以打包编译帮你打开相关的服务了~

//package.json
"scripts": {
    "start": "webpack-dev-server"
 },

Hot Module Replacement

有时候我们需要做的是,改了该模块的代码,浏览器不刷新,只更新该的模块代码上去,Hot Module Replacement就能帮我们实现这个效果。

//webpack.config.js
const webpack = require('webpack')
 devServer: {
    ...
    hot: true,//使用 Hot Module Replacement
    hotOnly: true, //Hot Module Replacement 出错的时候,浏览器照样不刷新
  },
  plugins: [
    ...
    new webpack.HotModuleReplacementPlugin() 
  ]

js模块代码更新的话还需要增加,css模块的话css-loader已经帮我们处理了,那像vue的文件修改vue-loader也已经做了相关的处理

if (module.hot) {
  module.hot.accept('./number.js', function() {
    // Do something with the updated library module...
  });
}

babel

我们在项目中为了更好的提高开发效率会使用一些ES6语法,虽然很方便快捷, 但是ES6语法在低版本浏览器并不完全支持,这样就会导致语法报错,所以我们需要借助babel来转发成ES5来兼容各个浏览器

// index.js
import "@babel/polyfill"; //引入polyfill:可实现ES6语法解释
const p = new Promise(() => {})

let newArr = [1,2,3]
newArr.map((item) => {
  return item
})

将其安装为开发依赖项

<!--正常业务使用babel-->
npm install --save-dev babel-loader @babel/core
npm install @babel/preset-env --save-dev

<!--使用polyfill-->
npm install --save @babel/polyfill

<!--使用插件-->
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
npm install --save @babel/runtime-corejs2
module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader", //只用建于和webpack通讯,并未来解决转换问题
        options: {
          // "presets": [["@babel/preset-env",{
          //   targets: {
          //     chrome: "67"
          //   },
          //   useBuiltIns: 'usage' //可以根据业务代码只转相关ES6,大大减少打包体积
          // }]] //用于转换ES6语法成ES5识别语法,但未能解释Promise map 等语法
          // "plugins": [
          //   [
          //     "@babel/plugin-transform-runtime",
          //     {
          //       "absoluteRuntime": false,
          //       "corejs": 2, //默认flase
          //       "helpers": true,
          //       "regenerator": true,
          //       "useESModules": false,
          //       "version": "7.0.0-beta.0"
          //     }
          //   ]
          // ]
        }
      },

我们可以使用presets的方式,也可以使用plugins的方式。当我们需要做第三方库或者插件的时候,我们就需要使用plugins的方式,因为presets的方式会造成全局污染,当我们配置项太多的时候,我们也可以在根目录抽出单独的文件.babelrc来配置

//.babelrc
{
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "absoluteRuntime": false,
        "corejs": 2, //默认flase
        "helpers": true,
        "regenerator": true,
        "useESModules": false,
        "version": "7.0.0-beta.0"
      }
    ]
  ]
}

使用webpack的配置文件(高级概念)

包含了 Tree Shaking,Code Spliting,打包环境区分,缓存,shimming 等内容,继续扩展 Webpack 的基础知识面。

Tree Shaking

<!--index.js-->
import { add } from './math' 
add(1,2)

<!--math.js-->
export const add = (a,b) => {
  console.log(a,b)
  return a + b
}
export const minus = (a,b) =>{
  console.log(a,b)
  return a - b
}

执行npx webpack打包后,打算dist/main.js可以看不到没有用到的minus方法也一并打包了,这种就造成我们打包的文件过于冗余,Tree Shaking就可以帮助我们解决这个问题,但是只能解决ES模块导入,像require其他导入方式就不行,因为ES是底层是导入静态资源,require是动态资源。

mode为development下配置增加

<!--webpack.config.js-->
module.exports = {
  mode: 'development',
  optimization: {
    usedExports: true //使用模块导入
  }
 }

配置后可以看到只到导出了add方法

import './index.css'文件怎么办,不就是会被忽略了,这个时候我们需要在package.json增加忽略的配置

{
   ...
  "sideEffects": false, 
}

如果是生成环境mode为production下,就不需要增加optimization配置,因为生成环境默认帮我们增加了,但是sideEffects还是要配置的。

Develoment 和 Production 模式的区分打包

项目上线我们就需要把mode Develoment 切换为 Production,我们可以在包文件分别配置不同的打包命令

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

两种模式有很多共总的打包配置,我们可以使用webpack-merge抽离公用的配置,新建文件夹build,在该目录新建三个文件webpack.dev.js webpack.prod.js webpack.common.js

<!--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', //入口文件 默认:src/index.js
  },
  output: { //出口文件 默认: dist/main.js
    filename: '[name].js', //输出的文件名 
    path: path.resolve(__dirname, 'dist') //输出的路径,只能是绝对路径
  },
  module: {
        ...
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './public/index.html'
    })
  ]
}
<!--webpack.dev.js-->
const commonCongif = require('./webpack.common')
const merge = require("webpack-merge");
const devCongif = {
  mode: 'development',
  devtool: 'eval-cheap-module-source-map',
  devServer: {
    contentBase: './dist',
    port: 9000, //服务端口号
    open: true, //首次打包编译自动打开浏览器
    hot: true,
    hotOnly: true,
  },
  optimization: {
    usedExports: true
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ]
}

module.exports = merge(commonCongif,devCongif)
<!--webpack.prod.js-->
const commonCongif = require('./webpack.common')
const merge = require("webpack-merge");
const prodCongif = {
  mode: 'production',
  devtool: 'cheap-module-source-map',
}

module.exports = merge(commonCongif,prodCongif)

Code Splitting

Code Splitting可以实现代码切割,按需加载。

  • 不使用Code Splitting方式:首次访问页面时,加载main.js(2mb),当页面业务逻辑发生改变时,又要加载2mb的内容
//index.js
import _ from 'loadsh' //1mb
console.log(_.join(['a','b','c'],'***'))
//此省略一万行业务代码
console.log(_.join(['a','b','c'],'***'))
  • 使用Code Splitting方式:main.js被拆成lodash.js(1mb),main.js(1mb),当页面业务逻辑发生改变时,只要加载main.js即可(1mb)
<!--loadsh.js-->
import _ from 'loadsh' //1mb
window._ = _

<!--index.js-->
console.log(_.join(['a','b','c'],'***'))
//此省略一万行业务代码
console.log(_.join(['a','b','c'],'***'))

//webpack.commom.js
module.exports = {
  entry: {
    loadsh: './src/loadsh.js',
    main: './src/index.js', //入口文件 默认:src/index.js
  },
}

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

  • 1.同步代码:只需要在webpack.commom.js中坐optimization的配置
  • 2.异步代码(类似import导入),无需做任何配置,会自动进行代码分割
    // 同步导入
import _ from 'loadsh' //1mb
console.log(_.join(['a','b','c'],'***'))
//此省略一万行业务代码
console.log(_.join(['a','b','c'],'***'))

//异步导入
function getComponent() {
  return import('loadsh').then(({ default: _ }) => {
    var elem = document.createElement('div')
    elem.innerHTML = _.join(['a','b','c'],'***')
    return elem
  })
}


getComponent().then(elem => {
  document.body.appendChild(elem)
})
//webpack.common.js
module.exports = {
    ...
    optimization: {
        splitChunks: {
          chunks: 'all'
        }
    }
}

splitChunks默认配置详解

 optimization: {
    splitChunks: {
      chunks: 'all', //代码切割的类型: async: 只切割异步代码  initial:只切割同步代码 all:两种都切割
      minSize: 30000, //切割的文件最小要求,单位kb
      maxSize: 0,//切割的文件最大要求,单位kb,超过部分会继续切分
      minChunks: 1, //最小需要切割的次数需求:至少需要切割一次
      maxAsyncRequests: 6,//按需加载时并行请求的最大数量。
      maxInitialRequests: 4,//入口点的最大并行请求数。
      automaticNameDelimiter: '~',//默认情况下,webpack将使用块的来源和名称生成名称(例如vendors~main.js)。此选项使您可以指定用于生成名称的定界符。
      cacheGroups: {//缓存组,同步切割时候会走该模块区分切割组
        defaultVendors: { 
          test: /[\\/]node_modules[\\/]/, //配置的模块:node_modules文件夹下的模块切割
          priority: -10 //切割优先级
        },
        default: { //如果没有达到上方的,就走到该默认组切割
          minChunks: 2,
          priority: -20, //切割优先级
          reuseExistingChunk: true //如果已存在切割模块,忽略这个组的切割
        }
      }
    }
  }

Lazy Loading 懒加载

懒加载可以实现按需加载,主要是通过import()来实现,如下点击事件才按需加载某个包
//异步导入

function getComponent() {
  return import(/* webpackChunkName: "lodash" */'loadsh').then(({ default: _ }) => {
    var elem = document.createElement('div')
    elem.innerHTML = _.join(['a','b','c'],'***')
    return elem
  })
}


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

Chunk

每个包生成的js文件就是一个chunk,minChunks配置就和这个有关minChunks: 1 最小需要切割的次数需求:至少需要切割一次,比如lodash文件切割至少一次。

打包分析

我们可以打包生成相关的json文件,利用第三方平台来查看分析我们打包的文件的各个方面

  • 步骤一:配置生成打包分析文件stats.json
<!--package.json-->
"scripts": {
   "dev-build": "webpack --profile --json > stats.json --config ./build/webpack.dev.js"
 }
  • 步骤二:利用第三方分析打包:http://webpack.github.io/analyse/

预取/预加载模块Preloading和Prefetching

在声明 import 时,使用下面这些内置指令,可以让 webpack 输出 "resource hint(资源提示)",来告知浏览器:

  • prefetch(预取):将来某些导航下可能需要的资源

  • preload(预加载):当前导航下可能需要资源
    下面这个 prefetch 的简单示例中:

<!--loadsh.js-->
function loadsh () {
  var elem = document.createElement('div')
  elem.innerHTML = 'test'  
}
export default loadsh
<!--index.js-->
//推荐写法:异步导入的方式可以提高代码的使用率,可以在浏览器控制面板Coverage看到使用率提高
document.addEventListener('click', () => {
  import(/*webpackPrefetch: true */ './loadsh').then(({default: _}) => {
    _()
  })
})


查看浏览器加载的资源,资源会在主核心加载完毕后加载了该模块,在点击的时候直接从缓存直接提取出来

css文件的代码切割

MiniCssExtractPlugin切割css

webpack在做打包的时候会把css文件打包在js里,我们就需要借助插件来帮我们对css文件进行切割

  • 文档

  • 可对css的引入文件进行代码分割

  • 会把 css 打包成单独的一个文件

  • 这个插件适合在production模式下做打包

注意tree shaking的代码分割,如果不设置会忽略css代码切割导致切割失败

optimize-css-assets-webpack-plugin对css进行代码压缩,具体详情配置查看文档,配置文件代码如下

const commonCongif = require('./webpack.common')
const merge = require("webpack-merge");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const prodCongif = {
  mode: 'production',
  devtool: 'cheap-module-source-map',
  module: {
    rules: [
      {
        test: /\.sass$/,
        use: [MiniCssExtractPlugin.loader, {
          loader: 'css-loader',
          options: {
            importLoaders: 2 // 0 => no loaders (default); 1 => postcss-loader; 2 => postcss-loader, sass-loader
            // modules: true //按模块化引入
          }
        }, 'sass-loader', 'postcss-loader'
        ]
      },
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader']
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css', //css文件切割插件
    })
  ]
}

module.exports = merge(commonCongif,prodCongif)

压缩css文件

1.安装

 npm install optimize-css-assets-webpack-plugin --save-dev

2.使用优化配置

<!--webpack.prod.js-->
const optimizeCss = require('optimize-css-assets-webpack-plugin');
 optimization: {
    minimizer: [
      new optimizeCss({}) //进行css代码代码
    ]
  },

Webpack 与浏览器缓存( Caching )

当我们访问浏览器的时候,第一次请求资源会从服务器拿去,当再次访问的时候,相同的文件名我们会直接从缓存中提取。但是我们会想,如果当我们修改了代码推上去之后,当前用户已经访问了,当用户再次刷新文件名字还是一样,它就不会更新最新的代码,而是从浏览器缓存中直接读取旧的。webpackoutput相关配置就可以帮助我们解决这个问题,让用户可以重新加载已经修改最新的代码模块。

<!--webpack.prod.js-->
output: {
  filename: '[name].[contenthash].js', //输出的文件名 
  chunkFilename: '[name].[contenthash].js' //chunk文件生成的名字
},

contenthash可以在打包的时候监听代码是否改变,改变则生成新的hash,不变则用上次的