webpack系列学习笔记

410 阅读9分钟

前言

本文整理了webpack前端资源构建工具以及静态模块打包器相关知识总结,如果对答案有不一样见解的同学欢迎评论区补充讨论,当然有问题,也欢迎在评论区指出。

参照引用尚硅谷webpack教程视频,以作记录

一、webpack简介

1、webpack 是什么?

(一)webpack 是一个前端资源构建工具。。

  • 像前端的某些资源文件(es6、less、sass、vue等),都需要各种小工具进行解析、转义后才能使用,然后就用到webpack这种包含这些小工具的一个大的构建工具,对这些文件做处理。

(二)webpack是一个静态模块打包器。。

//index.js文件(模块)
import $ from 'jquery';(小模块)
import './index.less';(小模块)
​
$('#title').click(()=>{
    $('body').css('backgroundColor','deepPink');
})
  1. index.js先是将jquery和less这些文件(这些文件叫做模块)引进来,然后形成一个chunk(代码块)
  2. 然后对chunk进行各项处理,如less编译成css,jquery编译成 js(浏览器能识别的语法),这些处理过程就叫做打包
  3. 打包后输出出去的文件,叫bundle.js

2、webpack五个核心概念

(一)Entry(入口)

指示webpack从哪个文件为入口开始打包

(二)Output(输出)

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

(三)Loader(转义)

让webpack能够去处理解析那些非js文件(webpack只能理解js)

(四)plugin(插件)

让webpack支持更多的功能,相当于扩展功能,像打包优化和压缩,重新定义环境中的变量等

  • 打包前清除原dist文件中的内容------clean-webpack-plugin
  • 可以自动打包生成html文件,并自动引入打包后的结果----html-webpack-plugin

(五)mode(模式)

  • 开发模式(development):能让代码本地调试运行
  • 生产模式(production):能让代码生产上线运行

3、初始化项目

初始化配置

npm init -y 进行初始化

npm install webpack webpack-cli -D 安装webpack到本地

编译运行

运行指令:

  • 开发环境: webpack ./src/index.js -o ./build/built.js --mode=development   webpack会以 ./src/index.js为入口文件开始打包,打包后输出到./build/built.js整体打包环境,是开发环境

  • 生产环境: webpack ./src/index.js -o ./build/built.js --mode=production   打包后的资源会被压缩,比开发环境小

结论
  • webpack能处理js/json资源,不能处理css/img等其他资源
  • 生产环境和开发环境将ES6模块化编译成浏览器能识别的模块
  • 生产环境比开发环境多一个压缩js代码。

4、打包资源

(一)打包样式资源
/*
  webpack.config.js  webpack的配置文件
    作用: 指示 webpack 干哪些活(当你运行 webpack 指令时,会加载里面的配置)
    所有构建工具都是基于nodejs平台运行的~模块化默认采用commonjs。
*/// resolve用来拼接绝对路径的方法
const { resolve } = require('path');
​
module.exports = {
  // ----------------webpack配置
  entry: './src/index.js', //入口文件
  output: {  // 输出
    filename: 'built.js',  // 输出文件名
    path: resolve(__dirname, 'build')  // 输出路径    __dirname nodejs的变量,代表当前文件的目录绝对路径
  },
  // loader的配置
  module: {
    rules: [
      // 不同文件必须配置不同loader处理
      {
        test: /.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      },
      {
        test: /.less$/,  // 匹配哪些文件
        use: [      //使用哪些loader进行处理,use中loader的执行顺序:从后往前(先less->css->style)
          'style-loader',   //创建style标签,将js中的样式资源插入进去,添加到head中生效
          'css-loader',  //将css文件变成common模块加载js中,里面内容是样式字符串
          'less-loader'
        ]
      }
    ]
  },
  // plugins的配置
  plugins: [],
  // mode模式
  mode: 'development', // 开发模式
  // mode: 'production'
}
(二)打包html资源
/*
  loader: 1. 下载   2. 使用(配置loader)
  plugins: 1. 下载  2. 引入插件(构造函数)  3. 使用(调用函数)
*/
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
​
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: []
  },
  plugins: [
    // html-webpack-plugin
    // 功能:默认会创建一个空的HTML,自动引入打包输出的所有资源(JS/CSS)
    // 需求:需要有结构的HTML文件
    new HtmlWebpackPlugin({
      // 复制 './src/index.html' 文件,并自动引入打包输出的所有资源(JS/CSS)
      template: './src/index.html'
    })
  ],
  mode: 'development'
};
(三)打包图片资源
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
​
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /.less$/,
        // 要使用多个loader处理用use
        use: ['style-loader', 'css-loader', 'less-loader']
      },
      {
        test: /.(jpg|png|gif)$/,
        // 使用一个loader ,必须下载 url-loader file-loader
        loader: 'url-loader',  // 处理图片资源   问题:默认处理不了html中img图片
        options: {
          // 图片大小小于8kb,就会被base64处理
          // 优点: 减少请求数量(减轻服务器压力)
          // 缺点:图片体积会更大(文件请求速度更慢)
          limit: 8 * 1024,
            
          // 问题:因为url-loader默认使用es6模块化解析,而html-loader引入图片是commonjs。
          // 解析时会出问题:[object Module]
          // 解决:关闭url-loader的es6模块化,使用commonjs解析
          esModule: false,
          // 给图片进行重命名,[hash:10]取图片的hash的前10位,[ext]取文件原来扩展名
          name: '[hash:10].[ext]'
        }
      },
      {
        test: /.html$/,
        // 处理html文件的img图片(负责引入img,从而能被url-loader进行处理)
        loader: 'html-loader'
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development'
};
(四)打包其它资源
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
​
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /.css$/,
        use: ['style-loader', 'css-loader']
      },
      // 打包其他资源如:iconfont图标  (除了html/js/css资源以外的资源)
      {
        // 排除css/js/html资源
        exclude: /.(css|js|html|less)$/,
        loader: 'file-loader',
        options: {
          name: '[hash:10].[ext]'
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development'
};
(五)其它常用loader

脚本转换编译:

  • babel-loader : 加载ES6+ 代码后使用Babel转义为ES5后,浏览器才能解析

  • typescript-loader : 加载Typescript脚本文件

框架:

  • vue-loader : 加载和转义vue组件,提取出其中的逻辑代码 script,样式代码style,以及HTML 模板template,再分别把他们交给对应的loader去处理
(六)devServer热更新
  • webpack-dev-server启动一个服务之后

  • 浏览器和服务端是通过websocket进行长连接

  • webpack监听源文件的变化,即当开发者保存文件时触发webpack的重新编译。每次编译都会生成hash值已改动模块的json文件已改动模块代码的js文件。编译完成后通过socket向客户端推送当前编译的hash戳

  • 客户端的websocket监听到有文件改动推送过来的hash戳,会和上一次对比。一致则走缓存。不一致则通过ajaxjsonp向服务端获取最新资源

const { resolve } = require('path');
​
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  module: {}
  plugins: [],
  mode: 'development',
​
  // 开发服务器 devServer:用来自动化(自动编译,自动打开浏览器,自动刷新浏览器~~)
  // 特点:只会在内存中编译打包,不会有任何输出
  // 启动devServer指令为:npx webpack-dev-server  (包需要下载)
  devServer: {
    contentBase: resolve(__dirname, 'build'),   // 项目构建后路径
    compress: true,   // 启动gzip压缩
    port: 3000,  // 端口号
    open: true  // 自动打开本地默认浏览器
  }
};
开发环境配置
/*
  开发环境配置:能让代码运行
    运行项目指令:
      webpack 会将打包结果输出出去
      npx webpack-dev-server 只会在内存中编译打包,没有输出
*/const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
​
module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      // loader的配置
      {
        // 处理less资源
        test: /.less$/,
        use: ['style-loader', 'css-loader', 'less-loader']
      },
      {
        // 处理css资源
        test: /.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        // 处理图片资源
        test: /.(jpg|png|gif)$/,
        loader: 'url-loader',
        options: {
          limit: 8 * 1024,
          name: '[hash:10].[ext]',
          // 关闭es6模块化
          esModule: false,
          outputPath: 'imgs'
        }
      },
      {
        // 处理html中img资源
        test: /.html$/,
        loader: 'html-loader'
      },
      {
        // 处理其他资源
        exclude: /.(html|js|css|less|jpg|png|gif)/,
        loader: 'file-loader',
        options: {
          name: '[hash:10].[ext]',
          outputPath: 'media'
        }
      }
    ]
  },
  plugins: [
    // plugins的配置
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development',
  devServer: {
    contentBase: resolve(__dirname, 'build'),
    compress: true,
    port: 3000,
    open: true
  }
};

5、webpack 生产环境的基本配置

处理css
  • 提取 css 成单独文件

mini-css-extract-plugin ,将a.css和b.css提取合并到单独文件main.js中

  • css 兼容性处理 (有些样式在浏览器中无法解析)

postcss-loader 和 postcss-preset-env 插件

  • 压缩 css

optimize-css-assets-webpack-plugin

处理js
  • js 语法检查

    • eslint-loader eslint eslint-config-airbnb-base(语法风格) eslint-plugin-import
    • 只检查自己写的源代码,第三方库不检查,需要设置exclude:/node_modules/ ,
    • 自动修复检查出来的格式问题,需要设置options:{ fix:true}
  • js 兼容性处理

babel-loader @babel/core @babel/preset-env @babel/polyfill core-js

  • js 和html代码压缩
plugins: [
    new HtmlwebpackPlugin({
        template: './ src /index.html',
        minify: {  //设置minify,压缩htm1代码
            collapsewhitespace: true,//移除空格
            removeComments: true//移除注释
        }
    })
],
mode: 'production'  //webpack的production模式,自带uglify.js,可以实现js代码压缩
生产环境配置
const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
​
// 定义nodejs环境变量:决定使用browserslist的哪个环境
process.env.NODE_ENV = 'production';
​
// 复用loader
const commonCssLoader = [
  MiniCssExtractPlugin.loader,
  'css-loader',
  {
    // 还需要在package.json中定义browserslist
    loader: 'postcss-loader',
    options: {
      ident: 'postcss',
      plugins: () => [require('postcss-preset-env')()]
    }
  }
];
​
module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /.css$/,
        use: [...commonCssLoader]
      },
      {
        test: /.less$/,
        use: [...commonCssLoader, 'less-loader']
      },
      /*
        正常来讲,一个文件只能被一个loader处理。
        当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:
          先执行eslint 在执行babel
      */
      {
        // 在package.json中eslintConfig --> airbnb
        test: /.js$/,
        exclude: /node_modules/,
        // 优先执行
        enforce: 'pre',
        loader: 'eslint-loader',
        options: {
          fix: true
        }
      },
      {
        test: /.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          presets: [
            [
              '@babel/preset-env',
              {
                useBuiltIns: 'usage',
                corejs: {version: 3},
                targets: {
                  chrome: '60',
                  firefox: '50'
                }
              }
            ]
          ]
        }
      },
      {
        test: /.(jpg|png|gif)/,
        loader: 'url-loader',
        options: {
          limit: 8 * 1024,
          name: '[hash:10].[ext]',
          outputPath: 'imgs',
          esModule: false
        }
      },
      {
        test: /.html$/,
        loader: 'html-loader'
      },
      {
        exclude: /.(js|css|less|html|jpg|png|gif)/,
        loader: 'file-loader',
        options: {
          outputPath: 'media'
        }
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/built.css'
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true
      }
    })
  ],
  mode: 'production'
};

6、webpack 性能优化

开发环境性能优化
  • 优化打包构建速度

    当修改css文件,连同js文件一起都重新打包了一次

    • HMR:hot module replacement 模块热替换,当一个模块变化,只会打包这一个模块

    • 样式文件:可以使用HMR功能:因为style-loader内部实现了~
      js文件:默认不能使用HMR功能 --> 需要修改js代码,添加支持HMR功能的代码
          注意:HMR功能对js的处理,只能处理非入口js文件的其他文件。
      html文件: 默认不能使用HMR功能.同时会导致问题:html文件不能热更新了~ (不用做HMR功能,html文件是主文件,包含所有的引用)
          解决:修改entry入口,将html文件引入  entry: ['./src/js/index.js', './src/index.html'],
      
    • mode: 'development',
      devServer: {
        contentBase: resolve(__dirname, 'build'),
        compress: true,
        port: 3000,
        open: true,
        hot: true// 开启HMR功能--当修改了webpack配置,新配置要想生效,必须重新webpack服务---解决了样式文件
      }
      
      if (module.hot) {// 一旦 module.hot 为true,说明开启了HMR功能。 --> 让HMR功能代码生效
        module.hot.accept('./print.js', function() { // ----解决了js文件
          // 方法会监听 print.js 文件的变化,一旦发生变化,其他模块不会重新打包构建。
          // 会执行后面的回调函数
          print();
        });
      }
      
  • 优化代码调试

    • source-map-----能定位到代码错误的地方

    • devServer: {
        contentBase: resolve(__dirname, 'build'),
        compress: true,
        port: 3000,
        open: true,
        hot: true
      },
      devtool: 'eval-source-map' //只需要加这行代码
      //eval-cheap-module-souce-map
      
        source-map: 一种 提供源代码到构建后代码映射 技术 (如果构建后代码出错了,通过映射可以追踪源代码错误)
        
          [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
      ​
          source-map:外部
            错误代码准确信息 和 源代码的错误位置
          inline-source-map:内联----会导致文件过大,不推荐
            只生成一个内联source-map
            错误代码准确信息 和 源代码的错误位置
          hidden-source-map:外部
            错误代码错误原因,但是没有错误位置
            不能追踪源代码错误,只能提示到构建后代码的错误位置
          eval-source-map:内联
            每一个文件都生成对应的source-map,都在eval
            错误代码准确信息 和 源代码的错误位置
          nosources-source-map:外部
            错误代码准确信息, 但是没有任何源代码信息
          cheap-source-map:外部
            错误代码准确信息 和 源代码的错误位置 
            只能精确的行
          cheap-module-source-map:外部
            错误代码准确信息 和 源代码的错误位置 
            module会将loader的source map加入
      ​
          内联 和 外部的区别:1. 外部生成了文件,内联没有 2. 内联构建速度更快
      ​
      ✔✔✔✔开发环境:速度快,调试更友好
            速度快(eval>inline>cheap>...)
              eval-cheap-souce-map
              eval-source-map
            调试更友好  
              souce-map
              cheap-module-souce-map
              cheap-souce-map
      ​
            推荐的两种--> eval-source-map 调试更友好  / eval-cheap-module-souce-map  性能可以做到最好
      ​
      ✔✔✔✔生产环境:源代码要不要隐藏? 调试要不要更友好
            内联会让代码体积变大,所以在生产环境不用内联
            nosources-source-map 全部隐藏
            hidden-source-map 只隐藏源代码,会提示构建后代码错误信息
      ​
            推荐的两种--> source-map / cheap-module-souce-map
      
生产环境性能优化
  • 优化打包构建速度

    • oneOf

    • babel缓存

    • 多进程打包

      开启多进程打包。 
      进程启动大概为600ms,进程通信也有开销。
      只有工作消耗时间比较长,才需要多进程打包
      
    • externals

      禁止一些模块打包进来

      mode: 'production',
      externals: {
        // 拒绝jQuery被打包进来
        jquery: 'jQuery'
      }
      
    • dll

  • 优化代码运行的性能

    • 缓存(hash-chunkhash-contenthash)

    • tree shaking-----去除无用代码

      前提:1. 必须使用ES6模块化  2. 开启production环境
      作用: 减少代码体积
      ​
      在package.json中配置 
            "sideEffects": false 所有代码都没有副作用(都可以进行tree shaking)
              问题:可能会把css / @babel/polyfill (副作用)文件干掉
            "sideEffects": ["*.css", "*.less"]  这样就会保留css、less文件
      
    • code split

      // 单入口 --- 单页面
      // entry: './src/js/index.js',
      entry: { ---- 多页面
        // 多入口:有一个入口,最终输出就有一个bundle
        index: './src/js/index.js',
        test: './src/js/test.js'
      },
      
      /*
        1. 可以将node_modules中代码单独打包一个chunk最终输出
        2. 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk
      */
      optimization: {
        splitChunks: {
          chunks: 'all'
        }
      },
      mode: 'production'
      
      //index.js
      /*
        通过js代码,让某个文件被单独打包成一个chunk
        import动态导入语法:能将某个文件单独打包(即test文件单独打包)
      */
      import(/* webpackChunkName: 'test' */'./test')
        .then(({ mul, count }) => {
          // 文件加载成功~
          // eslint-disable-next-line
          console.log(mul(2, 5));
        })
        .catch(() => {
          // eslint-disable-next-line
          console.log('文件加载失败~');
        });
      
    • 懒加载/预加载

      console.log('index.js文件被加载了~');
      ​
      // import { mul } from './test';document.getElementById('btn').onclick = function() {
        // 懒加载~:当文件需要使用时才加载~ ---- 但当使用某个模块,模块又非常大时,加载的就会慢
        // 预加载 prefetch:会在使用之前,提前加载js文件,使用时读取缓存 ---- 兼容性较差
        // 正常加载可以认为是并行加载(同一时间加载多个文件)  
        // 预加载 prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
        import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
          console.log(mul(4, 5));
        });
      };
      
    • PWA 渐进式网络开发应用程序(离线可访问)

    在离线情况下,会从serviceWorker中和CacheStorage中获取资源

    workbox --> workbox-webpack-plugin
    
    plugins: [
        new WorkboxWebpackPlugin.GenerateSW({
            /*
            1. 帮助serviceworker快速启动
            2. 删除旧的 serviceworker
    
            生成一个 serviceworker 配置文件~
          */
            clientsClaim: true,
            skipWaiting: true
        })
    ],
    
    // 在index.js  注册serviceWorker
    // 处理兼容性问题
    if ('serviceWorker' in navigator) {
      window.addEventListener('load', () => {
        navigator.serviceWorker
          .register('/service-worker.js')
          .then(() => {
            console.log('sw注册成功了~');
          })
          .catch(() => {
            console.log('sw注册失败了~');
          });
      });
    }
    

总结

觉得写得好的,对你有帮助的,可以分享给身边人,知识越分享越多,千万不要吝啬呀

后续更新前端其它知识总结,请关注我,整理好,分享给你们,我们一起学前端