# 从 0 开始配置 webpach4.0

# 1.webpack 的演化

# 2.CommonJS--> AMD

CommonJS 在浏览器内并不适用,因为 require() 的返回是同步的,意味着有多个依赖的话需要一个一个依次下载,阻塞了 js 脚本执行 所以在 CommonJS 的基础上定义了 AMD,使用异步回调语法并行下载多个依赖

  require(['./b','./c'],function(b,c){
    var n = b.square(2)
    console.log(c)
  })

相应的导出语言可以这样写

 define(['./d'],function(d){
   return d.PI
 })

define() 和 require() 的区别是,define()必须要在回调函数中返回一个值作为导出的内容;require()不需要导出内容,因此回调函数不需要返回值,也无法作为被依赖项被其他文件导入,因此用于入口文件,比如页面加载 a.js

 <script src="js/require.js" data-main="js/a"></script>

# 3.上手一个简单的 SPA 应用

PS:在写下 new webpack.XXXX 时,总是会报 XXXX undfine,主要原因是在引入时,写成了 const {webpack} = requrie('webpack) 只要去掉 webpack 外面的打括号就可以了

# 3-1 安装 node

# 3-2 初始化一个项目

  • 1.创建一个文件夹,然后在文件夹内搭建项目,先看一下目录结构
 |-- dist                 打包输出目录,只需要部署该文件夹到生产环境
 |-- package.json         项目配置信息
 |-- node_module          npm 安装的依赖包
 |-- src                  源代码
 |  |-- components        可复用的模块
 |  |-- index.html        入口 html
 |  |-- index.js          入口 js
 |  |-- shared            公共函数库
 |  |-- views             页面
 |-- webpack.config.js    webpack配置文件
  • 2.初始化项目 npm init

# 3-3.给项目装上 eslint 代码检测工具

 npm install eslint eslint-config-enough babel-eslint eslint-loader --save-dev

npm install 可以同时安装多个包,中间用空格隔开即可

--save-dev 会将安装包和版本号记录到 package.json 中的 devDependencies 对象中

--save 会记录到 dependencies 对象中

我们在提交 git 代码时,会将 node_module 在.gitignore 文件中忽略,在提交到公共库中后,其他人可以直接根据 package.json 配置文件直接执行 npm install

它会根据 devDependencies 和 dependencies 字段,把记录的包的相应版本下载下来

这里的 eslint-config-enough 是配置文件,它规定了代码规范,如果需要它生效,需要在 package.json 文件中添加内容:

 {
   "eslintConfig":{
     "extends":"enough",
     "env":{
       "browser":true,
       "node":true
     }
   }
 }

banel-eslint 是 eslint-config-enough 依赖的语法解析库,替代 eslint 默认的解析库以支持还未标准化的语言

eslint-loader 用于在 webpack 编译的时候检查代码,如果有错误,webpack 会报错

项目里安装了 eslint 还没用,我们的 IDE 和编辑器也需要安装 eslint 插件支持

# 3-4 写几个页面

  • 编辑 src/index.html 文件

    写简单的结构,html body head 等标签 不需要写 script 标签,webpack 会自动打包加入

# 3-5 安装 webpack 和 babel

  • 将 webpack 和它的插件安装到项目中
  • webpack 是 webpack 的核心库
  • webpack-cli 是 webpack 命令行
  • webpack-serve 是 webpack 用来开发调试的服务器,有了它就不用配置 nginx 了
  • html-webpack-plugin,html-loader,css-loader,style-loader 等是打包 html,css 文件
  • file-loader,url-loader 是打包二进制文件插件
 npm install webpack webpack-cli webpack-serve html-webpack-plugin html-loader css-loader style-loader file-loader url-loader --save-dev

为了让不支持 ES6 的浏览器可以正常运行,需要安装 babel,它会将 ES6 源代码转化为 ES5

 npm install babel-core babel-preset-env babel-loader --save-dev
  • babel-core 是 babel 的核心编译器,babel-preset-env 是一个配置文件,可以转换 ES2017..到 ES5
  • babel-preset-env 打包不生效,需要在配置文件中加入 babel 配置:
 {
   "babel":{
     "presets":["env"]
   }
 }

# 3-6 配置 webpack

配置 webpack 需要创建 webpack.config.js 文件,由于该文件是在 node 中运行,所以不支持 ES6 语法

# 3-7 运行项目

配置完成,开始运行项目:

  • 测试环境 webpack-serve
 ./node_modules/.bin/webpack-serve webpack.config.js

不同平台的配置命令不同,windows 自行搜索

  • 生产环境 webpack-serve
 ./node_modules/.bin/webpack-cli

会生成对应 dist 文件夹

执行长命令比较麻烦,可以在 package.json 中的 javascript 中配置对应的命令

# 4.进阶配置

# 4-1 设置静态资源的 url 路径前缀

  • 1.给资源文件加一个前缀,先修改 webpack 的配置
 {
   output:{
     publicPath:'/assets/'
   }
 }
  • 2.修改 webpack-serve
 if(dev){
   module.exports.serve = {
     port:8080,
     host:'0.0.0.0',
     dev:{
       publicPath:'/assets/'
     },
     add: app => {
       app.use(convert(history({
         index:'/assets/'
       })))
     }
   }
 }

# 4-2 各个页面分开打包

  • webpack 可以使用异步加载文件方式引用模版,可以使用 async/await 和 dynamic import 来实现

  • 加载 path 路径页面,使用 async/await 语法,动态加载页面,创建页面实例,调用方法加载到页面

  • regenerator-runtime 是 regenerator 的运行库,babel 通过插件 transform-regenerator 使用 regenerator 将 generator 函数和 async/await 语法转化为 ES5,需要运行库才能正确执行

  • 因为 import() 没有正式进入标准,需要使用 syntax-dynamic-import 来解析语法,我们安装 babel-preset-stage-2

 npm install regenerator-runtime babel-preset-stage-2 --save-dev
  • package.json 也需要做修改:
 {
   "babel":{
     "presets":[
       "env",
       "stage-2"
     ]
   }
 }

  • 然后修改 webpack 的配置:
 {
   // 代码中的资源文件会合并为一个包,我们称这个包为chunk,每个chunk里面又很多的modules,chunkFilename用来配置这个chunk 输出文件名
   // [chunkhash]是这个chunk的hash值,文件会时刻发生变化
   // 还有一个占位符,编译时每个chunk会有一个id
   output:{
     chunkFilename:'[chunkhash].js'
   }
 }

# 4-3 第三方库和业务代码分开打包

分开打包,在更新业务代码时,可以借助浏览器缓存,不需要再去下载第三方库。webpack4 的最大改进就是引用 chunk 自动拆分,满足以下条件,chunk 就会被拆分

  • 新的 chunk 能被复用,或者模块来自 node_modules 目录
  • 新的 chunk 大于 30k(min+gz 压缩前)
  • 按需加载 chunk 的并发请求数量小于等于 5 个
  • 页面初始化加载时的并发请求数量小于等于 3 个

分开打包注意以下几个参数即可

 plugins:[
   new webpack.HashedModuleIdsPlugin()
 ],
 optimization: {
   // chunk文件名发生改变时,会导致引用这个chunk文件也发生变化
   // runtimeChunk设置为true时,webpack会将chunk文件名全部存到一个单独的chunk中
   // 这样更新一个文件只会影响它所在的chunk和runtimeChunk,避免引用这个chunk的文件也发生改变

   runtimeChunk:true,
   splitChunks: {
     // 默认entry的chunk不会被拆分
     // 使用了html-wenpack-plugin 动态插入<script>标签,entry被拆成多个chunk也能自动被插入到html中
     // 将chunks配置为all,就把entry chunk也拆分了

     chunks:'all'
   }
 }

# 4-4 输出在 entry 文件的 hash

chunkFilename 使用[chunkhash]防止浏览器读取错误缓存,那么 entry 同样需要加上 hash,使用 webpack-serve 启动开发环境时,entry 是没有[chunkhash]的,用了会报错,所以只有在 wenpack-cli 时使用[chunkhash]

 output: {
   filename:dev ? '[name].js' : '[chunkhash].js'
 }

每个 entry 可以定义由多个 module 组成,这些 module 可以依次执行

 {
   entry: {
     main: './src/index.js',
     wendor: ['jquery','loadsh']
   }
 }

entry 引用规则和 import 一致,会寻找 node_module 包,然后结合 CommonsChunkPlugin 把 vendor 定义的 module 从业务代码中分离打包出一个单独的 chunk

 {
   entry: {
     main: ['./src/index.js']
   }
 }

webpack 会给这个 entry 指定名字为 main,[name]就是 entry 的名字;[hash]占位符,这个是整个编译过程的总 hash 值,但不是单文件的 hash 值,项目中任何一个文件的变动,都会改变这个 hash 值,此处的[hash]占位符是始终存在的,我们 不希望修改一个文件就改变所有的输出文件,这个不利于浏览器的缓存,所以这个 hash 意义不大

# 4-5 开发环境关闭 performance.hints

测试环境会报 warning,建议每个输出的 js 文件大小不要超过 250k,但开发环境包含了 sourcemap 并且代码压缩一般都会超过这个大小,所以需要将这个 waining 关闭,webpack 配置加入:

 {
   performance: {
     hints:dev ? false : 'warning'
   }
 }

# 4-6 配置 favicon.png

在 src 目录中放一张 favicon.png,然后在 src/index.html 的 head 中插入

 <link rel="icon" type="image/png" href="favicon.png">

修改 webpack 配置

 mudule: {
   rules: [
     test
   ]
 }

# 4-7 开发环境允许其他电脑访问

internal-ip

# 4-8 打包时自定义部分参数

例如:在 webpack-serve 写死监听接口,会导致端口冲突,一般会去 webpack.config.js 去暴力修改配置

这种情况一般只影响测试换,生产环境配置不同,生产环境可能需要配置一个 CDN 地址

  • default.js 生产环境
 module.exports = {
   publicPath: 'http://cdn.example.com/assets/'
 }
  • dev.js 默认开发环境
 mudule.exports = {
  publicPath:'/assets/',
  serve: {
    port: 8089
  }
 }

  • local.js 个人本地开发环境修改部分参数
 const config  = require('./dev')
 config.serve.port = 8087
 mudule.exports = config
  • webpack 修改配置

    这里关键是 npm run 传进来的自定义参数可以通过 process.env.npmconfig*获得,参数中的-会被转成_ 还有一点,不要把个人的配置文件提交到 git,所以在配置文件中忽略

# 4-9 webpack-serve 处理带后缀名的文件的特殊规则

当处理带有后缀名的请求,connect-history-api-fallback 会认为它是一个实际存在的文件,找不到时不会 fallback 到 html 文件,而是返回 404 要处理这种情况,需要将配置项 disableDotRule:true 的规则禁用,当访问文件不存在时可以 fallback 到 index.html 文件中

 module.exports = {
   add: app => {
     app.use(convert(history({
       disableDotRulr: true,
       htmlAcceptHeaders: ['text/html','application/xhtml+xml']
     })))
   }
 }

# 4-10 代码中插入环境变量

业务代码中,有些变量在开发环境和生产环境是不一样的,比如域名,api 地址,开发环境还需要打印调试信息等

我们可以使用 DefinePlugin 插件在打包时往代码中插入需要的环境变量

  new webpack.DefinePlugin({
    DEBUG:dev,
    VERSION: JSON.stringify(pkgInfo.version),
    CONFIG:  JSON.stringify(config.runtimeConfig)
  })

DefinePlugin 插件原理比较简单,如过在代码中写 consolo.log(DEBUG),它会做类似的处理:console.log(DEBUG).replace("DEBUG",true) 最后生成 console.log(true)

需要注意的一点: 这里的 VERSION,如果我们不对 pkgInfo.version 做 JSON.stringify()处理,最后就会生成 console.log(1.0.0),做了转变后,才会将带上引号,webpack 在打包压缩时,会对代码做优化

# 4-11 简化 import 路径

文件 a 引入文件 b 时,b 的路径相对于 a 的文件目录,如果 ab 文件在不同目录且 b 藏的比较深时,写起来就会比较麻烦

 import b from '../../../components/b'

为了方便,我们可以定义一个路径别名

 resolve: {
   alias: {
     '~': resolve(__dirname, 'src')
   }
 }

这样我们可以以 src 目录为基础路径来 import 文件了

 import b form '~/components/b'

html 文件中 img 标签没办法使用这个功能,但是在 html-loader 中的一个 root 参数可以作为目录解析

  root:resolve(__dirname,'src')

  <img src="/favicon.png">

此时就不需要担心文件和文件路径的问题了 但是此处会有一个问题,配置好 root 后,html-loader 会将注释代码 里面的内容也解析出来,要注意一下

# 4-12 优化 babel 编译后的代码性能

babel 编译后的代码一般会造成性能损失,babel 提供了一个 loose 的选项,编译后的代码不需要完全遵守 ES6,简化编译后的代码,会提高代码的执行效率

 "babel": {
    "presets": [
      [
        "env",
        {
          "loose": true
        }
      ],
      "stage-2"
    ]
  },

注意:这样做就有兼容性的风险,导致源码的执行结果和编译后的结果不一致,如果代码没有遇到实际的效率瓶颈,尽量不要使用该模式

# 4-13 使用 webpack 自带的 ES6 模块处理功能

按照目前的配置,babel 会把 ES6 模块自定义转化为 CommonJS 自定义,但是 webpack 自己可以处理 import 和 export,并且 webpack 处理 import 时会做代码优化,把没用到的代码删除,因此我们可以通过 babel 提供的 module:false 选项把 commonjs 模块功能关掉

 "babel": {
    "presets": [
      [
        "env",
        {
          "modules": false
        }
      ],
      "stage-2"
    ]
  },

# 4-15 使用 autoprefixer 自动创建 css 的 vendor prefixes

css 有一个麻烦的问题,比较新的 css 属性在各个浏览器中加前缀,我们可以使用 autoprefixer 工具自动创建这些浏览器规则:

 :fullscreen a{
   display: flex
 }

autoprefixer 就会自动编译为

 :webpack-full-screen a{
   display: -webkit-box;
   display: flex
 }
 :-moz-full-screen a {
    display: flex
  }
  :-ms-fullscreen a {
      display: -ms-flexbox;
      display: flex
  }
  :fullscreen a {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex
  }

首先,安装

 npm install postcss-loader autoprefixer --save-dev

autoprefixer 是一个 postcss 插件,所以要安装 postcss 的 webpack loader;修改一下 webpack 的 css rule

 {
    // 匹配css文件
    test: /\.css$/,
    use: ['css-loader', 'style-loader', 'postcss-loader']
  },

然后创建文件 postcss.config.js

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

# 5 使用 webpack 打包多页面应用(Multiple-Page Application)

多页面应用也可以用 webpack 打包,MPA 意味着,并不是每一个单一的 html 入口和 js 入口,而是每一个页面对应一个 html 和多个 js,所以可以将结构改为

 |--- dist
 |--- package.json
 |--- node_modules
 |--- src
 |    |--- components
 |    |--- shared
 |    |--- favicon.ico
 |    |-- pages                          页面放这里
 |          |--- foo                     编译后生成 http://localhost:8080/foo.html
 |          |     |--- index.html
 |          |     |--- index.js
 |          |     |--- style.css
 |          |     |--- pic.png
 |          |--- bar                     编译后生成 http://localhost:8080/bar.html
 |                |--- index.html
 |                |--- index.js
 |                |--- style.css
 |                |--- baz               编译后生成 http://localhost:8080/bar/baz.html
 |                      |--- index.html
 |                      |--- index.js
 |                      |--- style.css
 |--- webpack.config.js

这里的每个页面都是从<!DOCTYPE html> 开头 到 </html> 结束的页面,这里的文件都是用 html-webpack-plugin 处理,index.js 是每个页面的业务逻辑,作为每个页面的入口 js 配置到 entry 中,这里需要 glob 库来把这些文件都筛选出来批量处理,为了使 optimization.splitChunks 和 optimization.runtimeChunk 功能,这里需要安装几个 webpack 插件

 npm install glob html-webpack-include-sibling-chunks-plugin uglifyjs-webpack-plugin mini-css-extract-plugin optimize-css-assets-webpack-plugin --save-dev

webpack 需要修改,entry 和 htmlPlugins 也会遍历 pages 目录生成

在开发环境中,为了在修改 html 文件后,网页可以自动刷新,还可以把 html 文件加入到 entry 中,但是不要在生成环境这样处理

# 6.总结

这次主要是根据 查看原文章 做参考,对 webpack 做了一次从零开始的配置,如果需要编译 react/vue 时,无非是安装一些 loader 和 plugin