webpack全面总结

153 阅读5分钟

image.png

webpack的由来

由于浏览器解析不了es6及以上的语法,无法编译less/sacc等,所以我们需要各种插件去es6编译es5、将less编译成css,比较杂乱,所以就有了webpack将这些插件组合在一起

webpack是什么?

我们从官网上可以知道:本质上,webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。

简单来说就是一个模块化打包工具,将不同的资源和文件,进行打包,合并在一个文件里。

webpack的工作原理

我们先来简单说一下webpack的工作原理:

在一个配置文件中,指明对某些文件进行编译、压缩、组合等任务。把你的项目当成一个整体,通过一个给定的主文件(index.js),webpack将从这个文件开始找到你的项目的所有的依赖文件,使用loaders处理他们,最后打包为一个浏览器可以识别的js文件。

webpack的理念就是一切皆模块化,把一堆的css文件和js文件放在一个总的入口文件,通过require引入,剩下的事情webpack会处理,包括所有模块的前后依赖关系,打包、压缩、合并成一个js文件,公共代码抽离成一个js文件、某些自己指定的js单独打包,模块可以是css/js/imsge/font等等。

webpack的作用

(1)重新加载编译,将浏览器不认识的语法编译成浏览器认识的语法。less编译成css,ES6语法转换成ES5。

(2)减少io请求。发送请求,会发挥一个html到浏览器,这时打开控制台会发现html页面通过script,link等标签引用的静态,浏览器会再次发出请求去获取这些资源。如何webpack打包,将所有的静态资源都合并好了,减少了io请求。

具体作用

  1. webpack可以根据模板生成HTML,并自动处理上面的css/js引用路径
  2. webpack可以自动处理<img>里面的图片路径,css里面背景图的路径,字体引用
  3. webpack可以开启本地服务器,一边改写代码,一边自动更新页面内容
  4. webpack可以编译jsx es6 sass less coffescript等,并添加md5、sourcemap等辅助
  5. webpack可以异步加载内容,不需要时不加载到DOM
  6. webpack可以配合vue.js和react.js等框架开发。

webpack的5个核心概念

1、entry入口
入口指示webpack以哪个文件为入口文件开始打包,分析构建内部依赖图

2.output输出
输出指示webpack打包后的资源bundles输出到哪里,以及如何命名

3.loader(翻译官的作用)
loader可以让webpack能够处理那些非js文件(webpack自身只理解js、json,现webpack5可以处理图片资源)

4.plugins
插件plugins可以用于执行范围更广的任务。插件的范围包括:从打包优化和压缩、一直到重新定义环境中的变量

5.mode
模式指示webpack使用相应模式的配置

image.png

/*
  webpack.config.js是webpack的配置文件
  作用:告诉webpack干哪些活(运行webpack指令时,会加载里面的配置)
  所有的构建工具都是基于nodejs平台运行的,模块化默认采用commonjs
*/
 
// resolve用来拼接绝对路径的方法
const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
 
module.exports = {
  // webpack配置
  // 入口文件
  entry: './src/index.js',
  // 输出
  output: {
    // 输出文件名
    filename: 'built.js',
    // 输出路径 __dirname是nodejs的变量,代表当前文件webpack.js目录的绝对路径02打包样式资源
    // 输出到build目录下去
    path: resolve(__dirname, 'build')
  },
  // loader的配置(1.下载2.使用)
  module: {
    rules: [
      {
        // test:使用正则匹配文件
        test: /\.css$/,
        // use:使用哪些loader进行处理,执行顺序是倒序依次执行,比如会先使用css-loader再使用style-loader
        use: [
          // style-loader会创建style标签,将js中的样式资源插入进行,添加到head中生效
          'style-loader',
          // css-loader会将css文件变成commonjs模块加载js中,里面内容是样式字符串
          'css-loader'
        ]
      },
      {
        test: /\.less$/,
        use: [
          'style-loader',
          'css-loader',
          // 将less文件编译成css文件,需要安装less和less-loader
          'less-loader'
        ]
      },
      // 处理图片资源【webpack5会自动处理图片资源 !!加上以下配置反而图片显示不出来,webpack4可以用以下配置】
      // {
      //   test: /\.(jpg|png|gif)$/,
      //   // 使用多个loader时可以用use:[],如果只使用一个loader可以直接写loader:‘xxx’
      //   // url-loader依赖file-loader,所以需要下载两个包
      //   loader: 'url-loader',
      //   // 图片配置信息
      //   options: {
      //     // 一般会对小图片进行base64处理,这里是当图片小于8kb时,就会被base64处理成字符串
      //     // 优点:减少请求数量(减轻服务器压力)
      //     // 缺点:图片体积会变大导致问价请求速度变慢
      //     limit: 10 * 1024, //根据项目情况来定,假如项目中小图片是9kb,难么这里可以设置10*1024
      //     // 在webpack4中ulr-loader默认使用es6模块化解析,而html-loader引入图片是commonjs
      //     // 解析时会出现问题:src内容会变为[object module]
      //     // 解决办法:关闭url-loader的es6模块化,使用commonjs解析
      //     esModule: false,
      //     // 打包后的图片的名称是hash值,字符比较长,可以自定义命名
      //     // 意思为:取hash值的前10位,[ext]是指取文件原来扩展名
      //     name: '[hash:10].[ext]'
      //   }
      // },
      // html-loader处理html中的img图片资源(负责引入img标签,然后交由url-loader进行解析处理)
      {
        test: /\.html$/,
        loader: 'html-loader'
      },
      // 打包其他资源(除了html/css/js以外的资源)
      {
        // exclude:排除html,css,js资源的其他资源都适用file-loader(比如字体图标iconfont)
        exclude: /\.(html|css|js)$/,
        loader: 'file-loader'
      }
    ]
  },
  // 插件(1.下载2.引入3.使用)
  plugins: [
    // html-webpack-plugin插件会打包html文件
    // 功能:会默认创建一个空的html,自动引入打包后输出的所有资源(js/css)
    new HtmlWebpackPlugin({
      // template模版:意味复制./src/index.html文件,并自动引入打包后输出的所有资源
      template: './src/index.html'
    })
  ],
  // 模式
  mode: 'development'
}

webpack打包优化的方法

1、 按需加载

1.1 路由组件按需加载

const router = [\  {\    path: '/index',\    [component](https://so.csdn.net/so/search?q=component&spm=1001.2101.3001.7020): resolve => require.ensure([], () => resolve(require('@/components/index')))\
  },\
  {\
    path: '/about',\
    component: resolve => require.ensure([], () => resolve(require('@/components/about')))\
  }\
]\

1.2 第三方组件和插件。按需加载需引入第三方组件

// 引入全部组件\
import ElementUI from 'element-ui'\
import 'element-ui/lib/theme-chalk/index.css'\
Vue.use(ElementUI)

// 按需引入组件\
import { Button } from 'element-ui'\
Vue.component(Button.name, Button)

1.3 对于一些插件,如果只是在个别组件中用的到,也可以不要在 main.js 里面引入,而是在组件中按需引入

// 在main.js引入\
import Vue from vue\
import Vuelidate from 'vuelidate'\
Vue.use(Vuelidate)

// 按组件按需引入\
import { Vuelidate } from 'vuelidate'

2、优化 loader 配置

  • 优化正则匹配
  • 通过 cacheDirectory 选项开启缓存
  • 通过 include、exclude 来减少被处理的文件
module: {\
  rules: [\
    {\
      test: /\.js$/,\
      loader: 'babel-loader?cacheDirectory',\
      include: [resolve('src')]\
    }\
  ]\
}

3、优化文件路径——省下搜索文件的时间

  • extension 配置之后可以不用在 require 或是 import 的时候加文件扩展名,会依次尝试添加扩展名进行匹配。
  • mainFiles 配置后不用加入文件名,会依次尝试添加的文件名进行匹配
  • alias 通过配置别名可以加快 webpack 查找模块的速度。
resolve: {\
    extensions: ['.js', '.vue', '.json'],\
    alias: {\
      'vue$': 'vue/dist/vue.esm.js',\
      '@': resolve('src'),\
    }\
  },

4、生产环境关闭 sourceMap

  • sourceMap 本质上是一种映射关系,打包出来的 js 文件中的代码可以映射到代码文件的具体位置,这种映射关系会帮助我们直接找到在源代码中的错误。
  • 打包速度减慢,生产文件变大,所以开发环境使用 sourceMap,生产环境则关闭。

5、代码压缩

  • UglifyJS: vue-cli 默认使用的压缩代码方式,它使用的是单线程压缩代码,打包时间较慢
  • ParallelUglifyPlugin: 开启多个子进程,把对多个文件压缩的工作分别给多个子进程去完成 两种方法使用如下:
plugins: [\
  **new UglifyJsPlugin**({\
    uglifyOptions: {\
      compress: {\
        warnings: false\
      }\
    },\
    sourceMap: true,\
    parallel: true\
  }),

 **new ParallelUglifyPlugin**({\
    //缓存压缩后的结果,下次遇到一样的输入时直接从缓存中获取压缩后的结果并返回,\
    //cacheDir 用于配置缓存存放的目录路径。\
    cacheDir: '.cache/',\
    sourceMap: true,\
    uglifyJS: {\
      output: {\
        comments: false\
      },\
      compress: {\
        warnings: false\
      }\
    }\
  })\
]

打包速度和打包后的文件大小对比

image.png

6、提取公共代码

  • 相同资源重复被加载,浪费用户流量,增加服务器成本。
  • 每个页面需要加载的资源太大,导致网页首屏加载缓慢,影响用户体验。 webpack3 使用 CommonsChunkPlugin 的实现:
plugins: [\
  new [webpack](https://so.csdn.net/so/search?q=webpack&spm=1001.2101.3001.7020).optimize.CommonsChunkPlugin({\
    name: 'vendor',\
    minChunks: function(module, count) {\
      console.log(module.resource, `引用次数${count}`)\
      //"有正在处理文件" + "这个文件是 .js 后缀" + "这个文件是在 node_modules 中"\
      return module.resource && /\.js$/.test(module.resource) && module.resource.indexOf(path.join(__dirname, './node_modules')) === 0\
    }\
  }),\
  new webpack.optimize.CommonsChunkPlugin({\
    name: 'common',\
    chunks: 'initial',\
    minChunks: 2\
  })\
]\

webpack4 使用 splitChunks 的实现:

module.exports = {\
  optimization: {\
    splitChunks: {\
      cacheGroups: {\
        vendor: {\
          priority: 1, //添加权重\
          test: /node_modules/, //把这个目录下符合下面几个条件的库抽离出来\
          chunks: 'initial', //刚开始就要抽离\
          minChunks: 2 //重复2次使用的时候需要抽离出来\
        },\
        common: {\
          //公共的模块\
          chunks: 'initial',\
          minChunks: 2\
        }\
      }\
    }\
  }\
}

7、CDN 优化

  • 随着项目越做越大,依赖的第三方 npm 包越来越多,构建之后的文件也会越来越大。
  • 再加上又是单页应用,这就会导致在网速较慢或者服务器带宽有限的情况出现长时间的白屏。 1、将 vue、vue-router、vuex、element-ui 和 axios 这五个库,全部改为通过 CDN 链接获取,在 index.html 里插入 相应链接。
<head>\
  <link rel="stylesheet" href="https://cdn.bootcss.com/element-ui/2.0.7/theme-chalk/index.css" />\
</head>\
<body>\
  <div id="app"></div>\
  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>\
  <script src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.min.js"></script>\
  <script src="https://cdn.bootcss.com/vuex/3.1.0/vuex.min.js"></script>\
  <script src="https://cdn.bootcss.com/vue-router/3.0.2/vue-router.min.js"></script>\
  <script src="https://cdn.bootcss.com/element-ui/2.6.1/index.js"></script>\
  <!-- built files will be auto injected -->\
</body>

2、在 webpack.config.js 配置文件

module.exports = {\
 ···\
    externals: {\
      'vue': 'Vue',\
      'vuex': 'Vuex',\
      'vue-router': 'VueRouter',\
      'element-ui': 'ELEMENT',\
      'Axios':'axios'\
    }\
  },

3、卸载依赖的 npm 包,npm uninstall axios element-ui vue vue-router vuex

4、修改 main.js 文件里之前的引包方式

/ import Vue from 'vue'\
// import ElementUI from 'element-ui'\
// import 'element-ui/lib/theme-chalk/index.css'\
// import VueRouter from 'vue-router'

import App from './App.vue'\
import routes from './router'\
import utils from './utils/Utils'

Vue.use(ELEMENT)\
Vue.use(VueRouter)

const router = new VueRouter({\
  mode: 'hash', //路由的模式\
  routes\
})

new Vue({\
  router,\
  el: '#app',\
  render: h => h(App)\
})\

8、使用 HappyPack 多进程解析和处理文件

  • 由于运行在 Node.js 之上的 Webpack 是单线程模型的,所以 Webpack 需要处理的事情需要一件一件的做,不能多件事一起做。
  • HappyPack 就能让 Webpack 把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。
  • HappyPack 对 file-loader、url-loader 支持的不友好,所以不建议对该 loader 使用。 使用方法如下:
  1. HappyPack 插件安装: npm i -D happypack
  2. webpack.base.conf.js 文件对 module.rules 进行配置
**webpack.base.conf.js:**

module: {\
  rules: [\
    {\
 **    test: /\.js$/,**\
      use: ['happypack/loader?id=babel'],\
      include: [resolve('src'), resolve('test')],\
      exclude: path.resolve(__dirname, 'node_modules')\
    },\
    {\
      **test: /\.vue$/,**\
      use: ['happypack/loader?id=vue']\
    }\
  ]\
}\

3.在生产环境 webpack.prod.conf.js 文件进行配置

**webpack.prod.conf.js ​​​​​​​:**

const HappyPack = **require('happypack')**\
// 构造出共享进程池,在进程池中包含5个子进程\
const HappyPackThreadPool = HappyPack.**ThreadPool({ size: 5 })**\
plugins: [\
  new HappyPack({\
    // 用唯一的标识符id,来代表当前的HappyPack是用来处理一类特定的文件\
    **id: 'babel',**\
    // 如何处理.js文件,用法和Loader配置中一样\
    loaders: ['babel-loader?cacheDirectory'],\
   **threadPool**: HappyPackThreadPool\
  }),\
  new HappyPack({\
   **id: 'vue',**  // 用唯一的标识符id,来代表当前的HappyPack是用来处理一类特定的文件\
    loaders: [\
      {\
        loader: 'vue-loader',\
        options: vueLoaderConfig\
      }\
    ],\
    **threadPool**: HappyPackThreadPool\
  })\
]

总结

  1. 比较实用的方法: 按需加载,优化loader配置,关闭生产环境的sourceMap,CDN优化。
  2. vue-cli已做的优化: 代码压缩,提取公共代码,再优化空间不大。
  3. 根据项目实际需要和自身开发水平选择优化方法,必须避免因为优化产生bug。