淘先锋技术网

首页 1 2 3 4 5 6 7

前言:webpack是我们现代前端最常见的模块打包工具,webpack 是一个模块打包器,主要目的是在浏览器上打包 JavaScript 文件。特性:打包 CommonJs 和 AMD 模块(以及绑定),可创建单个或多个按需加载的块,以减少初始加载时间,在编译期间会解决依赖关系,减少了运行时的大小,加载器可以在编译时预处理文件,如 coffee-script 到 javascript。

小编也是从闲余时间零开始搭建webpack,作为演示讲解一些常用的功能块和属性方法

在这里插入图片描述
了解Bable js编译器

webpack进阶-自定义插件原理

体积减少80%!释放webpack tree-shaking的真正潜力

package.json文件描述

相关属性依赖版本号,并非是最新版本,实际开发请重新安装依赖

{
  //从name到private都是package的配置信息,也就是我们在脚手架搭建中输入的项目描述
  "name": "shop",//项目名称:不能以.(点)或者_(下划线)开头,不能包含大写字母,具有明确的的含义与现有项目名字不重复
  "version": "1.0.0",//项目版本号:遵循“大版本.次要版本.小版本”
  "description": "webpack project",//项目描述
  "author": "njt",//作者名字
  "private": true,//是否私有
  "scripts": { //scripts中的子项即是我们在控制台运行的脚本的缩写
   //①webpack-dev-server:启动了http服务器,实现实时编译;
   //inline模式会在webpack.config.js入口配置中新增webpack-dev-server/client?http://localhost:8080/的入口,使得我们访问路径为localhost:8080/index.html(相应的还有另外一种模式Iframe);
   //progress:显示打包的进度
    "start": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",  
    "start": "npm run dev",//与npm run dev相同,直接运行开发环境
    "build": "node build/build.js"//使用node运行build文件
    "analyz": "NODE_ENV=production npm_config_report=true npm run build", // 查看打包体积大小分布
  },
  "dependencies": { //②dependencies(项目依赖库):在安装时使用--save则写入到dependencies
    "vue": "^2.5.2",//vue.js
    "vue-router": "^3.0.1"//vue的路由插件
  },
  //和devDependencies(开发依赖库):在安装时使用--save-dev将写入到devDependencies
  "devDependencies": {
    "autoprefixer": "^7.1.2",//autoprefixer作为postcss插件用来解析CSS补充前缀,例如 display: flex会补充为display:-webkit-box;display: -webkit-flex;display: -ms-flexbox;display: flex。
    //babel:以下几个babel开头的都是针对es6解析的插件。用最新标准编写的 JavaScript 代码向下编译成可以在今天随处可用的版本
    "babel-core": "^6.22.1",//babel的核心,把 js 代码分析成 ast ,方便各个插件分析语法进行相应的处理。
    "babel-helper-vue-jsx-merge-props": "^2.0.3",//预制babel-template函数,提供给vue,jsx等使用
    "babel-loader": "^7.1.1",//使项目运行使用Babel和webpack来传输js文件,使用babel-core提供的api进行转译
    "babel-plugin-syntax-jsx": "^6.18.0",//支持jsx
    "babel-plugin-transform-runtime": "^6.22.0",//避免编译输出中的重复,直接编译到build环境中
    "babel-plugin-transform-vue-jsx": "^3.5.0",//babel转译过程中使用到的插件,避免重复
    "babel-preset-env": "^1.3.2",//转为es5,transform阶段使用到的插件之一
    "babel-preset-stage-2": "^6.22.0",//ECMAScript第二阶段的规范
    "chalk": "^2.0.1",//用来在命令行输出不同颜色文字
    "copy-webpack-plugin": "^4.0.1",//拷贝资源和文件
    "css-loader": "^0.28.0",//webpack先用css-loader加载器去解析后缀为css的文件,再使用style-loader生成一个内容为最终解析完的css代码的style标签,放到head标签里
    "mini-css-extract-plugin": "^0.9.0",//将一个以上的包里面的css文本提取到单独文件中
    "file-loader": "^1.1.4",//③打包压缩文件,与url-loader用法类似
    "friendly-errors-webpack-plugin": "^1.6.1",//识别某些类别的WebPACK错误和清理,聚合和优先排序,以提供更好的开发经验
    "html-webpack-plugin": "^2.30.1",//简化了HTML文件的创建,引入了外部资源,创建html的入口文件,可通过此项进行多页面的配置
    "node-notifier": "^5.1.2",//支持使用node发送跨平台的本地通知
    "ora": "^1.2.0",//加载(loading)的插件
    "portfinder": "^1.0.13",//查看进程端口
    "postcss-import": "^11.0.0",//可以消耗本地文件、节点模块或web_modules
    "postcss-loader": "^2.0.8",//用来兼容css的插件
    "postcss-url": "^7.2.1",//URL上重新定位、内联或复制
    "webpack-parallel-uglify-plugin": "^1.1.2",//压缩js文件
    "url-loader": "^0.5.8",//压缩文件,可将图片转化为base64
    "vue-loader": "^13.3.0",//VUE单文件组件的WebPACK加载器
    "vue-style-loader": "^3.0.1",//类似于样式加载程序,您可以在CSS加载器之后将其链接,以将CSS动态地注入到文档中作为样式标签
    "vue-template-compiler": "^2.5.2",//这个包可以用来预编译VUE模板到渲染函数,以避免运行时编译开销和CSP限制
    "webpack": "^4.43.0",//打包工具
    "webpack-cli": "^3.3.12",
    "webpack-bundle-analyzer": "^2.9.0",//可视化webpack输出文件的大小
    "webpack-dev-server": "^3.11.0",//提供一个提供实时重载的开发服务器
    "webpack-merge": "^4.2.2"//它将数组和合并对象创建一个新对象。如果遇到函数,它将执行它们,通过算法运行结果,然后再次将返回的值封装在函数中
  },
  "engines": { //engines是引擎,指定node和npm版本
    "node": ">= 6.0.0",
    "npm": ">= 3.0.0"
  },
  "browserslist": [ //限制了浏览器或者客户端需要什么版本才可运行
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ]
}

1. 代码示例 webpack.base.js公共编译模块:入口、出口、编译模块配置、代码块分割、性能警告、插件等

const os = require('os')
const path = require('path');
const webpack = require('webpack');
const HappyPack = require('happypack')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); // webpack4官网未同步,改变了引用方式
const MiniCssExtractPlugin = require("mini-css-extract-plugin")

const happyThreadPool = HappyPack.ThreadPool({
  size: os.cpus().length
})
module.exports = {
  entry: { // 项目编译的文件入口地址,也是配置多文件入口地址
    main: './src/index.js',
  },

  output: { // 项目编译文件的出口地址
  	// 编译生成的js文件存放在根目录下的js目录下,如果js文件夹不存在就自动创建
    filename: 'js/[name].[chunkhash].js', // 「入口分块(entry chunk)」的文件名模板(出口分块)
    path: path.resolve(__dirname, '../dist'), // 所有输出文件的目标路径
    publicPath: "/assets/", // 输出解析文件的目录,url 相对于 HTML 页面
	// 常用与封装第三方库使用,项目内注释掉
	// library: "MyLibrary", // 导出库(exported library)的名称
    // libraryTarget: "umd", // 通用模块定义
  },

  resolve: { // 解析模块请求的选项
    extensions: [".js", ".json", ".jsx", ".css"], // 使用的扩展名
    alias: {  // 模块别名列表
      '@': path.resolve(__dirname, '../src')
    }
  },
   
  module: {
    rules: [
      {
        test: /\.js$/, // 这里是匹配条件,每个选项都接收一个正则表达式或字符串
        loader: 'happypack/loader?id=happyBable', // 应该应用的 loader,它相对上下文解析
        exclude: /node_modules\/(?!module-[^\s]+)/, // exclude 是必不匹配选项(优先于 test 和 include)
        include: [
          path.resolve(__dirname, '../src'),
          path.resolve(__dirname, 'node_modules/webpack-dev-server/client'),
          /module-[^\s]+/i,
        ]
      },
      {
        test: /\.css$/,
        use: ['style-loader', MiniCssExtractPlugin.loader, 'css-loader']
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: ['file-loader']
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: ['file-loader']
      }
    ]
  },

  optimization: { // 代码分割文件依赖公共模块部分
    splitChunks: {
      chunks: "all",   //initial、async和all
      minSize: 30000,   //形成一个新代码块最小的体积
      minChunks: 2,  //在分割之前,这个代码块最小应该被引用的次数
      maxAsyncRequests: 5,   //按需加载时候最大的并行请求数
      maxInitialRequests: 3,   //最大初始化请求数
      automaticNameDelimiter: '~',   //打包分割符
      name: 'commons', //打包后的名字
     // 会抽离出第三库的代码,生成一个vendors.db5b3f3dc26a5ebe685c.js文件,且每次打包第三库的hash也不会变化,
    // 这样就会利用浏览器的缓存机制,达到一个缓存优化,除非第三方库的版本变化,会重新生成一个新的文件,这样浏览
    // 器也会重新请求新的文件。
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,   //用于控制哪些模块被这个缓存组匹配到、它默认会选择所有的模块
          priority: -10,   //缓存组打包的先后优先级
          name: 'vendors',
          minSize: 3000
        },
        styles: {
          name: 'styles',
          test: /\.(c|le)ss$/,
          chunks: 'all',
          enforce: true,
        },
        default: {
          minChunks: 2,
          priority: -20,
          minSize: 3000
        }
      }
    },
  },

  performance: { // 性能提示
	maxEntrypointSize: 2.5 * 1024 * 1024, // 整数类型(以字节为单位)
	maxAssetSize: 3 * 1024 * 1024 // 整数类型(以字节为单位)
  },

  plugins: [
    new CleanWebpackPlugin(), // 在每次构建前清理 /dist 文件夹。
    new HtmlWebpackPlugin({ // 为您生成HTML文件,其余参数详看https://github.com/jantimon/html-webpack-plugin
      inject: 'body',
      minify: { // 压缩html
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
      },
      template: path.resolve(__dirname, '../index.html')
    }),
    new HappyPack({ // 将文件解析任务分解成多个子进程并发执行
      id: 'happyBable',  // id 标识符,要和 rules 中指定的 id 对应起来
      loaders: ['babel-loader?cacheDirectory'], // babel-loader开启缓存;需要使用的 loader,用法和 rules 中 Loader 配置一样
      threadPool: happyThreadPool, // 对象,代表共享进程池, 以防止资源占用过多
      verbose: true // 是否允许 happypack 输出日志,默认是 true
    }),
    new MiniCssExtractPlugin({ // 为每个包含 CSS 的 JS 文件创建一个单独的 CSS 文件,并支持 CSS 和 SourceMap 的按需加载
      filename: "css/[name].[chunkhash].css",
      ignoreOrder: false,
    })
  ],
};

补充:MiniCssExtractPlugin插件,提取js文件中的css文件,以link链接在index.html文件中引入,注意:当style-loader 和 css-loader方式在生产环境构建不生效时,原因:1.是否开启 tree shaking, 将package.json文件中的"sideEffects"去掉或者改为自己匹配的文件;2. 查看rules规则配置的loader是否正确,MiniCssExtractPlugin插件不支持style-loader。3.配置完MiniCssExtractPlugin插件会生成多个bundle.css包,可以通过配置cacheGroups单独打包成一个文件。

2. 代码示例 webpack.dev.js开发环境编译模块:mode、devServer、plugins等

const ip = require('ip') // 获取本地ip地址
const path = require('path')
const merge = require('webpack-merge'); // 在环境特定配置 merge() 合并 dev 和 prod常见配置
const common = require('./webpack.base.js'); // 编译环境公共模板
const packageConfig = require('../package.json')
const CopyWebpackPlugin = require('copy-webpack-plugin') // 拷贝静态文件资源
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');

const devConfig = {
  mode: 'development', // 环境区分:"production" | "development" | "none"
  devtool: 'inline-source-map', // 通过在浏览器调试工具(browser devtools)中添加元信息(meta info)增强调试,详细配置请点击https://www.webpackjs.com/configuration/devtool/
  devServer: {
    contentBase: '../dist', // 服务的访问地址,也是你本地编译完后的访问地址
    port: 8081, // 端口的位置
    quiet: true, // 关闭编译终端出错,这与FriendlyErrorsWebpackPlugin相关联,控制台编译出错
    overlay: true, // 编译出现错误时,将错误直接显示在页面上
    historyApiFallback: { // 404响应页面 默认index.html
      rewrites: [{
        from: /.*/,
        to: path.posix.join('/', '../index.html')
      }, ],
    }
  }
};

// 启动消息提示在终端显示
const createNotifierCallback = () => {
  const notifier = require('node-notifier')
  return (severity, errors) => {
    if (severity !== 'error') return false;
    const error = errors[0]
    const filename = error.file && error.file.split('!').pop()
    notifier.notify({
      title: packageConfig.name,
      message: severity + ': ' + error.name,
      subtitle: filename || '',
      icon: path.join(__dirname, 'logo.png'),
      sound: true
    })
  }
}

module.exports = merge(common, devConfig, {
  plugins: [
    new FriendlyErrorsWebpackPlugin({ // 去除终端webpack多余编译信息使界面可观
      compilationSuccessInfo: {
        messages: [`Your application is running here: http://${ip.address()}:${devConfig.devServer.port}`],
      },
      onErrors: () => createNotifierCallback(),
      clearConsole: true,
    }),
     new CopyWebpackPlugin({
      patterns: [
        {
          from: path.resolve(__dirname, '../static'),
          to: "static"
        }
      ]
    }),
  ]
})
  • FriendlyErrorsWebpackPlugin 去除终端webpack多余编译信息使界面可观
    在这里插入图片描述

3. 代码示例 webpack.prod.js生产环境编译模块:mode、devtool、plugins等

const webpack = require('webpack');
const merge = require('webpack-merge');
const common = require('./webpack.base.js');
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = merge(common, {
  mode: 'production', // 生产环境,默认开启更小的代码量以加快网页的加载速度,对源代码进行压缩
  devtool: false,
  plugins: [
    new webpack.DefinePlugin({ //自定义一个plugin 生成当前环境下的一个变量
      'process.env.NODE_ENV': JSON.stringify('production')
    }),
    new ParallelUglifyPlugin({ // 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
      uglifyJS: {  // 传递给 UglifyJS 的参数
        output: {
          // 最紧凑的输出
          beautify: false,
          // 删除所有的注释
          comments: false,
        },
        warnings: false,  // 在UglifyJs删除没有用到的代码时不输出警告
        compress: {
          // 删除所有的 `console` 语句,可以兼容ie浏览器
          drop_console: true,
          // 内嵌定义了但是只用到一次的变量
          collapse_vars: true,
          // 提取出出现多次但是没有定义成变量去引用的静态值
          reduce_vars: true,
        }
      },
    }),
    new BundleAnalyzerPlugin()
  ]
});

4.补充知识点 Chunk理解
Chunk是指webpack里的一个代码块。对于module,Webpack可以看做是模块打包机,我们编写的任何文件,对于Webpack来说,都是一个个模块。而Chunk是webpack打包过程中,一堆Module的集合。Chunk和Bundle,Bundle就是我们最终输出的一个或多个打包文件。产生Chunk的三种途径: entry入口、异步加载模块、代码分割。

5.webpack热更新原理:

  1. Webpack编译期,为需要热更新的 entry 注入热更新代码(EventSource通信)
  2. 页面首次打开后,服务端与客户端通过 EventSource 建立通信渠道,把下一次的 hash 返回前端
  3. 客户端获取到hash,这个hash将作为下一次请求服务端 hot-update.js 和 hot-update.json的hash
  4. 修改页面代码后,Webpack 监听到文件修改后,开始编译,编译完成后,发送 build 消息给客户端
  5. 客户端获取到hash,成功后客户端构造hot-update.js script链接,然后插入主文档
  6. hot-update.js 插入成功后,执行hotAPI 的 createRecord 和 reload方法,获取到 Vue 组件的 render方法,重新 render 组件, 继而实现 UI 无刷新更新。

5.webpack编译原理

  1. 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
  2. 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;
  3. 确定入口:根据配置中的 entry 找出所有的入口文件;
  4. 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
  5. 完成模块编译:在经过第 4 步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
  7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

本篇文章只是对webpack常用到的属性做了相关注解,实际的开发中相关文章也有说明,常去敲一敲也会遇到一些不常见的问题,对成长也是一种帮助吧!常看看官网其余属性和插件也有说明。
最后推荐一篇文章webpack是怎么实现动态导入的