webpack

作者: 静静_5835 | 来源:发表于2020-08-10 21:42 被阅读0次

一、webpack基础

1. webpack的安装

注意:请先自行安装nodejs最新版的环境

  • 全局安装webpack
    npm i webpack webpack-cli -g
  • 项目中安装webpack(推荐)
    npm i webpack webpack-cli -D

2. webpack的使用

2.1 webpack-cli

npm 5.2以上的版本红提供了一个npx命令
npx想要解决的主要问题,就是调用项目内部安装的模板,原理就是在node_modules下的.bin目录中找到对应的命令执行
使用webpack命令:npx webpack
webpack4.0之后可以实现0配置打包构建,0配置的特点就是限制较多,无法自定义很多配置
开发中常用的还是使用webpack配置进行打包构建

2.2 webpack配置

webpack有四大核心概念

  • 入口(entry):程序的入口js
  • 输出(output):打包后存放的位置
  • loader:用于对模块的源代码进行转换
  • 插件(plugins):插件目的在于解决loader无法实现的其他事
  1. 配置 webpack.config.js
  2. 运行npx webpack
const path = require('path')
// webpack遵循commonJS规范
module.exports = {
  entry: './src/index.js',
  output: {
    // path.resolve()
    // path.resolve(__dirname, './dist/')
    // path.join(__dirname, './dist/')
    path: path.resolve('./dist/'),
    filename: 'bundle.js'
  },
  mode: 'development'
}

2.3 开发时自动编译工具

每次编译代码时,手动运行npm run build就会变得很麻烦。
webpack中有几个不同的选项,可以帮助你在代码发生变化后自动编译代码:

  1. webpack's Watch Mode
  2. webpack-dev-server
  3. webpack-dev-middleware
    多数场景下,可能需要使用webpack-dev-server

2.3.1 watch

webpack指令后面加上--watch参数即可

2.3.2 webpack-dev-server(推荐)

  1. 安装devServer:
    devServer需要依赖webpack,必须在项目依赖中安装webpack
    npm i webpack-dev-server webpack -D
  2. index.html中修改<script src="/bundle.js"></script>
  3. 运行:npx webpack-dev-server
  4. 运行:npx webpack-dev-server --hot --open --port 8090
  5. 配置:package.json的scripts:“dev”: "webpack-dev-server --hot --open --port 8090"
  6. 运行 npm run dev

devServer会在内存中生成一个打包好的bundle.js,专供开发时使用,打包效率高,修改代码后会自动重新打包以及刷新浏览器,用户体验非常好

以上是cli的方式设置devServer的参数

还可以通过配置文件对devServer的参数进行修改:

  1. 修改webpack.config.js
const path = require('path')
// webpack遵循commonJS规范
module.exports = {
  entry: './src/index.js',
  output: {
    // path.resolve()
    // path.resolve(__dirname, './dist/')
    // path.join(__dirname, './dist/')
    path: path.resolve('./dist/'),
    filename: 'bundle.js'
  },
  mode: 'development',
  devServer: {
    open: true,
    port: 3000,
    compress: true,
    hot: true,
    contentBase: './src'
  }
}
  1. 修改package.json的scripts:"webpack": "webpack-dev-server"
  2. 运行npm run dev

2.3.3 html插件

  1. 安装html-webpack-plugin 插件npm i html-webpack-plugin -D
  2. webpack.config.js中的plugins节点下配置
const HtmlWebpackPlugin = require('html-webpack-plugin')
plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'template.html'
    })
  ]

功能:

  1. devServer时根据模板在express项目根目录下生成html文件(类似于devServer生成内存中的bundle.js)
  2. devServer时自动引入bundle.js
  3. 打包时会自动生成index.html

2.3.4 webpack-dev-middleware

  1. 安装expresswebpack-dev-middleware:
    npm i expree webpack-dev-middleware -D
  2. 新建server.js
const express = require('express')
const webpack = require('webpack')
const webpackDevMiddleware = require('webpack-dev-middleware')
const config = require('./webpack.config')

const app = express()
const compiler = webpack(config)

app.use(webpackDevMiddleware(compiler, {
  publicPath: '/'
}))

app.listen(3000, function() {
  console.log('http://localhost:3000')
})
  1. 配置package.json中的scripts"server":"node server.js"
  2. 运行npm run server
    注意:如果要使用middleware,必须使用html-webpack-plugin插件,否则html无法正确输出到express服务器根目录

2.3.5 小结

只有在开发时,才需要使用自动编译工具,例如:webpack-dev-server
项目上线时都会直接使用webpack进行打包构建,不需要使用这些自动编译工具
自动编译工具只是为了提高开发体验

2.4 处理css

  1. 安装npm i css-loader style-loader -D
  2. 配置webpack.config.js
module: {
    rules: [
      {
        test: /\.css$/,
        // webpack读取loader时,是从右往左的读取,会将css文件先交给最右侧的loader来执行
        // loader的执行顺序是从右到左以管道的方式链式调用
        // css-loader: 解析css文件
        // style-loader: 将解析出来的结果,放到html中,使其生效 
        use: ['style-loader', 'css-loader']
      }
    ]
  }

2.5 处理less和sass

  1. 安装npm i less less-loader node-sass sass-loader -D
  2. 配置webpack.config.js
module: {
    rules: [
      {
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader']
      },
      {
        test: /\.s(a|c)ss$/,
        use: ['style-loader', 'css-loader', 'sass-loader']
      }
    ]
  }

2.6 处理图片和字体图标

  1. npm i file-loader url-loader -D
    url-loader 封装了file-loader,所以使用url-loader的时候一定要安装file-loader
{
        test: /\.(jpg|jpeg|png|bmp|gif)$/,
        // use: 'file-loader'
        use: {
          loader: 'url-loader',
          options: {
           // limit 如果图片大于5kb ,就以路径形式展示,小于的话就用base64格式展示
            limit: 5 * 1024
          }
        }
      },
      {
        test: /\.(woff|woff2|eot|svg|ttf)$/,
        use: 'file-loader'
      }

2.6.1 自定义打包图片目录和打包名称

  {
        test: /\.(jpg|jpeg|png|bmp|gif)$/,
        // use: 'file-loader'
        use: {
          loader: 'url-loader',
          options: {
            limit: 5 * 1024,
            outputPath: 'images',
            // name: 图片的名称
            // hash: 4 hash值保留4位数
            // ext:保留文件原后缀
            name: '[name]-[hash:4].[ext]'
          }
        }
      }

2.7 babel-loader

  1. npm i babel-loader @babel/core @babel/preset-env webpack -D
  2. 如果需要支持更高级的ES6语法,可以继续安装插件:
    npm i @babel/plugin-proposal-class-properties -D
      {
        test: /\.js$/,
        use: {
          loader: 'babel-loader',
          presets: ['@babel/env'],
          plugins: ['@babel/plugin-proposal-class-properties']
        },
        exclude: /node_modules/
      },

官方更建议的做法是在项目根目录下新建一个.babelrc的babel配置文件

{
  "presets": ["@babel/env"],
  "plugins": ["@babel/plugin-proposal-class-properties"]
}

2.9 插件

2.9.1 clean-webpack-plugin

  1. 安装npm i clean-webpack-plugin
  2. 引入插件
const CleanWebpackPlugin = require('clean-webpack-plugin')
  1. 使用插件
plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'template.html'
    }),
    new CleanWebpackPlugin()
  ]

2.9.2 cope-webpack-plugin

  1. 安装npm i cope-webpack-plugin
  2. 引入
const CopyWebpckPlugin = require('copy-webpack-plugin')
  1. 使用
plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'template.html'
    }),
    new CleanWebpackPlugin(),
    new CopyWebpckPlugin([
      {
          from: path.join(__dirname, 'assets')
          to: 'assets'
      }
    ])
  ]

2.10 BannerPlugin

这是一个webpack内置插件,用于给打包的js文件加上版权注释信息

  1. 引入webpack插件
const webpack = require('webpack')
  1. 创建插件对象
plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'template.html'
    }),
    new CleanWebpackPlugin(),
    new CopyWebpckPlugin([
      {
          from: path.join(__dirname, 'assets')
          to: 'assets'
      }
    ]),
    new webpack.BannerPlugin('李不言')
  ]

二、webpack高级配置

1. HTML中img标签的图片资源处理

  1. 安装npm i html-withimg-loader -S
  2. webpack.config.js文件中添加loader
      {
        test: /\.(htm|html)/,
        loader: 'html-withimg-loader'
      }

2. 多页应用打包

  1. webpack.config.js中修改入口和出口配置
// 第一步:修改成多路口
entry: {
    index: './src/index.js',
    other: './src/other.js'
  },
// 第二步:多入口无法对应一个固定的出口,所以filename为[name]变量
output: {
    path: path.resolve('./dist/'),
    filename: '[name].js'
  },
// 第三步:如果用了html插件,需要手动配置多入口对应的html文件,将指定其对应的输出文件
plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: './src/index.html',
      chunks: ['index']
    }),
    new HtmlWebpackPlugin({
      filename: 'other.html',
      template: './src/other.html',
      chunks: ['other']
    })
]

3. 第三方库的两种引用方式

  1. 通过expose-loader进行全局变量的注入
  2. 使用内置插件webpack.ProvidePlugin对每个模块的闭包空间,注入一个变量,自动加载模块,而不必到处importrequire
  • expose-loader (将库引入到全局作用域)
  1. 安装npm i expose-loader
  2. 配置
      {
        // require.resolve 用来获取模块的绝对路劲,所以这里的loader只会作用于jquery模块。并且只在bundle中找到它时,才能进行处理
        test: require.resolve('jquery'),
        use: {
          loader: 'expose-loader',
          options: '$'
        }
      }
  • webpack.ProvidePlugin (将库自动加载到每个模块)
  new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery'
    })

4. development / production不同配置文件打包

项目开发时一般需要使用两套配置文件,用于开发阶段打包(不压缩代码,不优化代码,增加效率)和上线阶段打包(压缩代码,优化代码,打包后直接上线使用)
抽取三个配置文件:

  • webpack.base.js
  • webpack.dev.js
  • webpack.prod.js

步骤如下:

  1. 将开发环境和生产环境公用的配置放入base中,不同的配置各自放入prod或dev文件中(例如mode)
  2. 然后在dev和prod中使用webpack-merge把自己的配置与base的配置合并后导出
  3. 将package.json中的脚本参数进行修改,通过--config手动指定特定的配置文件
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base')
module.exports = merge(baseConfig, {
  mode: 'development',
  devServer: {
    open: true,
    port: 3000,
    compress: true,
    hot: true,
    // contentBase: './src'
  }
})

5. 定义环境变量

除了区分不同的配置文件进行打包,还需要再开发时知道当前的环境是开发阶段或上线阶段,所以可以借助内置插件DefinePlugin来定义环境变量。最终可以实现开发阶段与上线阶段的api地址自动切换

  1. 引入webpack
const webpack = require('webpack')
  1. 创建插件对象,并定义环境变量
module.exports = merge(baseConfig, {
    mode: 'production',
    plugins: [
      new webpack.DefinePlugin({
        IS_DEV: 'true'
      })
    ]
})
  1. 在src打包的代码环境下可以直接使用

6. 跨域问题及常用解决方案

目前解决跨域的主要方法有:

  • jsonp(淘汰:利用script标签实现跨域,只能用于get请求)
  • cors(主流:后端控制)
  • http proxy
devServer: {
    open: true,
    port: 3000,
    compress: true,
    hot: true,
    // contentBase: './src',
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        pathRewrite: {
          '^/api': ''
        }
      }
    }
  },

三、webpack优化

1. production模块打包自带优化

  • tree shaking
    tree shaking是一个术语,通常用于打包时移动JavaScript中的未引用的代码(dead-code),他依赖于ES6模块系统中importexport静态结构特征
    开发时引入一个模块后,如果只使用其中一个功能,上线打包时只会把用到的功能打包进bundle,其他没用到的功能都不会打包起来,可以实现最基础的优化

  • scope hoisting
    scope hoisting的作用是将模块之间的关系进行结果推测,可以让webpack打包出来的代码文件更小、运行的更快
    scope hoisting的实现原理其实很简单:分析出模块之间的依赖关系,尽可能的把打散的模块合并到一个函数中去,但前提是不能造成冗余代码
    因此只有那些被引用了一次的模块才能被合并
    由于scope hoisting需要分析出模块之间的依赖关系,因此源码必须采用ES6模块化语句,不然无法生效
    原因和tree shaking一样

  • 代码压缩
    所有代码使用UglifyJsPlugin插件进行压缩、混淆

2. css优化

2.1 将css提取到独立文件中

mini-css-extract-plugin是用于将css提取为独立的文件的插件,对每个包含css的js文件都会创建一个css文件,支持按需加载css和sourceMap
只能用在webpack4中,有如下优势

  • 异步加载
  • 不重复编译,性能更好
  • 更容易使用
  • 只针对css
    使用方法:
  1. 安装
    npm i -D mini-css-extract-plugin
  2. 在webpack配置文件中引入插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
  1. 创建插件对象,配置抽离的css文件名,支持placeholder语法
new MiniCssExtractPlugin({
  fileName: '[name].css'
})
  1. 将原来配置的所有style-loader替换为MiniCssExtractPlugin.loader
rules: [
{
        test: /\.css$/,
        // webpack读取loader时,是从右往左的读取,会将css文件先交给最右侧的loader来执行
        // loader的执行顺序是从右到左以管道的方式链式调用
        // css-loader: 解析css文件
        // style-loader: 将解析出来的结果,放到html中,使其生效 
        // use: ['style-loader', 'css-loader']
        use: [MiniCssExtractPlugin.loader, 'css-loader']
      },
      {
        test: /\.less$/,
        // use: ['style-loader', 'css-loader', 'less-loader']
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
      },
      {
        test: /\.s(a|c)ss$/,
        // use: ['style-loader', 'css-loader', 'sass-loader']
        use: [MiniCssExtractPlugin.loader, 'style-loader', 'css-loader', 'sass-loader']
      }
]

2.2 自动添加css前缀

使用postcss,需要用到postcss-loaderautoprefixer插件

  1. 安装
npm i -D postcss-loader autoprefixer
  1. 修改webpack配置文件中的loader,将postcss-loader防在css-loader的右边,调用链从右到左
rules: [
      {
        test: /\.css$/,
        // webpack读取loader时,是从右往左的读取,会将css文件先交给最右侧的loader来执行
        // loader的执行顺序是从右到左以管道的方式链式调用
        // css-loader: 解析css文件
        // style-loader: 将解析出来的结果,放到html中,使其生效 
        // use: ['style-loader', 'css-loader']
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader']
      },
      {
        test: /\.less$/,
        // use: ['style-loader', 'css-loader', 'less-loader']
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader']
      },
      {
        test: /\.s(a|c)ss$/,
        // use: ['style-loader', 'css-loader', 'sass-loader']
        use: [MiniCssExtractPlugin.loader, 'style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
      }
]
  1. 项目根目录下添加postcss的配置文件:postcss.config.js
module.exports = {
  plugins: [require('autoprefixer')]
}

2.3 开启css压缩

需要使用optimize-css-assets-webpack-plugin插件来完成css压缩
但是由于配置css压缩时会覆盖掉webpack默认的优化配置,导致js代码无法压缩,所以还需要手动把JS代码压缩插件导入进来:terser-webpack-plugin

  1. 安装
    npm i -D optimize-css-assets-webpack-plugin terser-webpack-plugin
  2. 导入插件
const TerserJSPlugin = require('terser-webpack-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
  1. webpack配置文件中添加配置节点
optimization: {
      minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})]
    }

tips: webpack4默认才有的JS压缩插件为:uglifyjs-webpack-plugin, 在mini-css-extract-plugin上一个版本中还推荐使用该插件,但新版的v0.6建议使用teser-webpack-plugin来完成js代码压缩

3. js优化

Code Splitting 是webpack打包时用到的重要的优化特性之一,此特性能够把代码分离到不同的bundle中,然后按需加载或并行加载这些文件。代码分离可以用于获取更小的bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载事件。

有三种常用的代码分离方法:

  • 入口起点(entry points):使用entry配置手动地分离代码。
  • 防止重复(prevent duplication):使用SplitChunkPlugin去重和分离chunk
  • 动态导入(dynamic imports):通过模块的内联函数调用来分离代码

3.1 手动配置多入口

  1. 配置多入口
module.exports = {
  entry: {
    index: './src/index.js',
    other: './src/other.js'
  },
  output: {
    // path.resolve()
    // path.resolve(__dirname, './dist/')
    // path.join(__dirname, './dist/')
    path: path.resolve('./dist/'),
    filename: '[name].js'
  }
}
  1. 在mian.js和other.js 中都引入同一个模块,并使用其功能
main.js
import $ from 'jquery'
$(function(){('<div></div>').html('main').appendTo('body')})
other.js
import $ from 'jquery'
$(function(){('<div></div>').html('other').appendTo('body')})
  1. 修改package.json的脚本,添加一个使用dev配置文件进行打包的脚本(目的是不压缩代码检查打包的bundle时更方便)

这个方法会存在一些问题:

  • 如果入口chunks之间包含重复模块,那些重复的模块都会被引入到各个bundle中
  • 这种方法不够灵活,并且不能将核心应用程序逻辑进行动态拆分代码

3.2 抽取公共代码

tips:webpack v4以上使用的插件为 SplitChunksPlugin,以前使用的CommonsChunkPlugin已经被移除,最新版本的webpack只需要再配置文件中的optimization节点下添加一个splitChunks属性即可进行相关配置

  1. 修改webpack配置文件
optimization: {
  splitChunks: {
    chunks: 'all'
  }
}
  1. 运行npm run dev-build
  2. 此时可以看到jQuery已经单独打包到了一个文件中

3.3 动态导入(懒加载)

webpack4默认允许import语法动态导入的,但是需要babel的插件支持,最新版本babel插件包为:@babel/plugin-syntax-dynamix-import,以前老版本不是@babel开头,已经无法使用,需要注意
动态导入最大的好处是实现了懒加载,用到哪个模块才会加载哪个模块,可以提高SPA应用程序的首屏加载速度,Vue、React、Angular框架的路由懒加载原理一样

  1. 安装babel插件
    npm install -D @babel/plugin-syntax-dynamix-import
  2. 修改.babelrc配置文件,添加@babel/plugin-syntax-dynamix-import插件
{
  "presets": ["@babel/env"],
  "plugins": [
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-transform-runtime",
    "@babel/plugin-syntax-dynamic-import"
  ]
}
  1. 将jQuery模块进行动态导入
function getComponent() {
  return import('jquery').then({default: $}) => {
    return $('<div></div>').html('main')
  })
}
  1. 给按钮添加点击事件,点击后调用getComponent函数创建元素并添加到页面
window.onload = function() {
  document.getElementById('btn').onclick = function() {
  getComponent().then( (item) => {
    item.appendTo('body')
  })
}
}

3.4 SplitChunksPlugin配置参数

webpack4之后,使用SplitchunksPlugin插件替代了以前CommonsChunksPlugin

而SplitchunksPlugin的配置,只需要在webpack配置字文件中的optimization节点下的splitChunks进行修改即可,如果没有任何修改,则会使用默认配置

默认的SplitChunksPlugin配置适用于绝大多数用户

webpack会基于如下默认原则自动分割代码:

  • 公共代码块或来自node_modules文件夹的组件模块
  • 打包的代码块大小超过30k(最小化压缩之前)
  • 按需加载代码块时,同时发送的请求最大数量不应该超过5
  • 页面初始化时,同时发送的请求最大数量不超过3

默认配置如下:

module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async',  // 默认值为async 
      minSize: 20000,
      minRemainingSize: 0,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 30,
      maxInitialRequests: 30,
      automaticNameDelimiter: '~',
      enforceSizeThreshold: 50000,
      cacheGroups: {
        defaultVendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  }
};

4. 提高构建性能 --- noParse

在引入一些第三方模块时,例如jQuery、bootstrap等,我们知道其内部肯定不会依赖其他模块,因为最终我们用到的知识一个单独的js文件或者css文件

所以此时如果webpack再去解析他们的内部依赖关系,其实是非常浪费时间的,我们需要阻止webpack浪费精力去解析这些明知道没有依赖的库

可以在webpack配置文件的module节点下加上noParse,并配置正则来确定不需要解析依赖关系的模块

module: {
  noParse: /jquery|bootstrap/

5. 提高构建性能 --- IgnorePlugin

在引入一些第三方模块时,例如moment,内部会做i18n国际化处理,所以会包含很多语言包,而语言包打包时会比较占用空间,如果我们项目只用到中文,或者少数语言,可以忽略掉所有的语言包,然后按需引入语言包,从而使构建效率更高,打包生成的文件更小

需要忽略第三方内部依赖的其他模块,只需要三步:

  1. 首先找到moment依赖的语言包是什么
  2. 使用ignorePlugin插件忽略其依赖
  3. 需要使用某些依赖时自动手动引入

五、webpack原理

1. 目标

  • 了解webpack打包原理
  • 了解webpack的loader原理
  • 了解webpack的插件原理
  • 了解ast抽象语法树的应用
  • 了解tapable的原理
  • 手写一个简单的webpack

2. 项目准备工作

  1. 新建一个项目
  2. 新建bin目录,将打包工具主程序放入其中
    主程序的顶部应当有:#!/use/bin/env node标志,指定程序执行环境为node
  3. package.json中配置bin脚本
{
  "bin": "./bin/libuyan-park.js"
}
  1. 通过npm link链接到全局包中,供本地测试使用

相关文章

网友评论

      本文标题:webpack

      本文链接:https://www.haomeiwen.com/subject/vdlgrktx.html