Webpack4 使用入门(附 demo 源码)

849 阅读8分钟

webpack 是一个模块打包器,分析你的项目结构,找到 JavaScript 模块以及其它的一些浏览器不能直接运行的拓展语言(sass,less,typeScript等),并将其打包为合适的格式以供浏览器使用。

基本概念

entry, 工程的入口文件配置

1、字符串

entry: './src/index.js',

2、数组类型:拼接多个文件到一个文件

entry: ['./src/index.js',  './src/main.js'],

3、对象类型:分别打包

entry: {
   index: './src/index.js',
   main: './src/main.js'
}

output, 打包的输出的文件配置

path 告诉 Webpack 打包的结果存储在哪里。

publicPath 用于在生产模式下更新内嵌到 css、html 文件里的 url 值。

loader

loader 用于处理各种不同类型模块的处理器,将这些类型的模块处理为浏览器可运行和识别的代码,从而使 webpack 具有了强大而灵活的能力。

loader 本质上是一个函数,输入参数是一个字符串,输出参数也是一个字符串。webpack 会按照从右到左的顺序执行 loader。

  • babel-loader 将 es6 以上代码转换为 es5 代码;

  • style-loader:将 css 模块以 style 标签的形式加入到 html 中

  • css-loader:主要用来解析 css 中的静态资源,像import/require引入的方式一样去解释@import/url()

  • postcss-loader:可以对 css 进行各种处理,配合 autoprefixer 自动添加 css 前缀

  • autoprefixer:自动补全css前缀

  • sass-loader/less-loader:将sass/less代码转换为css

  • url-loader 和 file-loader 是一对用来处理图片、svg、视频、字体等静态资源文件的 loader

    url-loader 可以设置图片大小限制,当图片超过限制时,其表现行为等同于 file-loader,而当图片不超过限制时,则会将图片以 base64 的形式打包进 css 文件,以减少请求次数

plugins, 插件,在webpack打包过程中不同时机执行一些任务,比如清除打包目录、复制静态文件、抽取css文件

1、HtmlWebpackPlugin 插件可以用来生成包含你所有打包文件(js和css)的html文件,特别是你在打包文件名配置了hash,就不得不用这个插件了。

  new HtmlWebpackPlugin({
    template: './index.html', // 模板文件
    filename: 'index.html'
  })

​template: HtmlWebpackPlugin生成html文件的模板,如果简单的话可以直接通过其他配置项生成,不必单独提供一个html文件模板,详情可参考HtmlWebpackPlugin filename: 输出的html文件名,规则和output的filename相同 inject: 是否注入打包的文件,包括js和通过MiniCssExtractPlugin打包输出的css文件,默认为true minify: 是否压缩

2、HotModuleReplacementPlugin 正常情况下,hmr只会更新模块,不会触发页面刷新

3、MiniCssExtractPlugin 将一个 chunk 中的 css 抽取为一个单独的 css 文件,如果 chunk 中不包含css,则不生成文件。

  new MiniCssExtractPlugin({
    filename: "[name].[chunkhash:8].css",
    chunkFilename: "[id].css"
  }),

4、CopyWebpackPlugin 用来处理静态文件,可以将文件或者文件夹原封不动地移动到打包目录。

  new CopyWebpackPlugin([{
    from: path.join(__dirname, './favicon.ico'),
    to: path.join(__dirname, 'prod')
  }])

5、CleanWebpackPlugin 用来清除打包目录,主要用于每次重新生成的时候,清除残留文件。在文件有hash值的情况下,是必要的。

  new CleanWebpackPlugin('prod/*.*', {
    root: __dirname,
    verbose: true,
    dry: false
  })

6、splitChunks:代码拆分

chunks(默认是async):initial、async和all

minSize(默认是30000):形成一个新代码块最小的体积

minChunks(默认是1):被多少模块共享

maxAsyncRequests(默认是5):按需加载时候最大的并行请求数。

maxInitialRequests(默认是3):一个入口最大的并行请求数

test: 用于控制哪些模块被这个缓存组匹配到 RegExp、String和Function

name:打包的chunks的名字

priority :缓存组打包的先后优先级。

  splitChunks: {
    chunks: 'all',
    minChunks: 1,
    maxAsyncRequests: 5,
    maxInitialRequests: 3,
    automaticNameDelimiter: '-',
    name: true,
    cacheGroups: {
      react: {
        test: /react/,
        name: "react",
        priority: 1,
        minChunks: 1,
      }
    }
  }

7、runtimeChunk:runtime,用于管理被分出来的包

Mode

区分开发环境和生成环境, webpack会根据 mode 值自动帮你做一个不同的优化

1、production(默认值)

在DefinePlugin中将process.env.NODE_ENV设置为production

默认启用了如下插件:

  • FlagDependencyUsagePlugin:标记没有用到的依赖,
  • FlagIncludedChunksPlugin:给当前chunk包含的chunkid加入chunk名之中 ,
  • ModuleConcatenationPlugin:作用域提升,
  • NoEmitOnErrorsPlugin:在编译出现错误时,使用 NoEmitOnErrorsPlugin 来跳过输出阶段,
  • OccurrenceOrderPlugin:排序输出,通过模块调用次数给模块分配ids,常用的ids就会分配更短的id,使ids可预测,减小文件大小,
  • SideEffectsFlagPlugin:标记不包括副作用的模块,优化打包,
  • UglifyJsPlugin:用来对js文件进行压缩,从而减小js文件的大小,加速load速度

2、development

在DefinePlugin中将process.env.NODE_ENV设置为development

默认启用了如下插件:

  • NamedChunksPlugin:自定义chunks的名字,
  • NamedModulesPlugin:自定义模块的名字

3、mode的两种使用方式

配置: module.exports = { mode: 'production'}

命令行: webpack --mode=production

4、打包结果对比

production

2、development

实践

打包命令

Webpack 作为模块打包工具,提供两种用户交互接口:

  1. Webpack CLI tool:默认的交互方式(已随Webpack本身安装到本地) 有利于生产模式下打包,

  2. webpack-dev-server:一个Node.js服务器(需要从npm自行安装) 基于Express.js框架开发的web server,默认监听8080端口。

可以通过以下两种方式向 webpack-dev-server 传入参数:

1、通过 CLI 传参

webpack-dev-server --hot --inline

2、通过webpack.config.js文件的"devServer"对象

devServer: {
  inline: true,  // 刷新浏览器
  hot:true, // 重新加载改变的部分,HRM失败则刷新页面
  proxy: {    // 设置代理
    '/api/file/*': {
      target: 'https://www.google.com/',
      changeOrigin: true,    // 文件上传需要设置为 true
    },
  }
}

第一次打包 commonjs 模块化的处理 demo (init)

打包结果 webpack-test/dist/dist.js

总结

原来 webpack 就是把我们写的代码用一个包装函数包装了起来, 在执行__webpack_require__的时候调用一下包装函数,通过包装函数内部的代码重写了参数中参数 module 的 exports 属性, 获取到我们编写的模块的主体代码

由于 index.js 中有 require('./foo') 所以 index.js 生成的包装函数参数中多了__webpack_require__用于导入 foo 模块

es6 Module 模块化的处理 demo (es6 Module)

打包结果

总结

module.exports 变成了__webpack_exports__

使用 es6 模块导入语法(import)的地方, 给__webpack_exports__添加了属性__esModule

commonjs es6 module 混合 demo (混合)

打包结果

打包 html

html-loader html-webpack-plugin

代码

const path = require('path');

module.exports = {
  mode: 'development',
  // mode: 'production',
  // 这里是打包的入口文件(相对路径)
  entry: './index.js',
  output: {
    // 打包结果存放的位置(必须用绝对路劲)
    path: path.resolve(__dirname, 'dist'),
    // path: path.resolve('/Users/rainzhao/collect/webpack/demo/1', 'dist'),
    // 打包结果文件名称
    filename: 'bundle.js',
  },
};

打包 css

css-loader mini-css-extract-plugin

可以将 use 配置为一个数组,loader 从右往左依次执行,且前一个 loader 的结果是下一个 loader 的输入。最后一个 loader 的输出就是我们最终要的结果。 一个 sass 文件首先经过 sass-loader 处理,变成 css 文件,又经过 postcss-loader 处理,添加浏览器前缀等功能,接着交给 css-loader 去解析 css 文件引用的静态变量,最后由 style-loader 以 script 标签的形式加入到 html 中。

代码

const path = require('path')
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const HtmlWebpackPlugin = require('html-webpack-plugin')
const Webpack = require('webpack')

module.exports = {
  mode: 'development',
  // mode: 'production',
  // 这里是打包的入口文件(相对路径)
  entry: './src/index.js',
  output: {
    // 打包结果存放的位置(必须用绝对路劲)
    path: path.resolve(__dirname, 'dist'),
    // path: path.resolve('/Users/rainzhao/collect/webpack/demo/1', 'dist'),
    // 打包结果文件名称
    filename: 'index.[hash:8].js',
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader']
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].[chunkhash:8].css",
      chunkFilename: "[id].css"
    }),
    new Webpack.HotModuleReplacementPlugin(),
    new HtmlWebpackPlugin({
      template: './index.html', // 模板文件          
      filename: 'index.html'
    })
  ]

}

打包 less

less-loader postcss-loader autoprefixer

代码

const path = require('path')
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const HtmlWebpackPlugin = require('html-webpack-plugin')
const Webpack = require('webpack')


module.exports = {
  mode: 'development',
  // mode: 'production',
  // 这里是打包的入口文件(相对路径)
  entry: './src/index.js',
  output: {
    // 打包结果存放的位置(必须用绝对路劲)
    path: path.resolve(__dirname, 'img'),
    // path: path.resolve('/Users/rainzhao/collect/webpack/demo/1', 'dist'),
    // 打包结果文件名称
    filename: 'index.[hash:8].js',
  },
  module: {
    rules: [
      {
        test:/\.css/,
        use:[MiniCssExtractPlugin.loader,"css-loader",{
          loader: "postcss-loader",
          options: {
            plugins: () => [require('autoprefixer')]
          }
        }]
      },
      {
        test:/\.less$/,
        use:[MiniCssExtractPlugin.loader,"css-loader",{
          loader: "postcss-loader",
          options: {
            plugins: () => [require('autoprefixer')]
          }
        },"less-loader"]
      },
      {
        test: /\.(html)$/,
        use: {
          loader: 'html-loader',
          options: {
            attrs: ['img:src', 'img:data-src', 'audio:src'],
            minimize: true
          }
        }
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].[chunkhash:8].css",
      chunkFilename: "[id].css"
    }),
    new Webpack.HotModuleReplacementPlugin(),
    new HtmlWebpackPlugin({
      template: './index.html', // 模板文件          
      filename: 'index.html'
    })
  ]

}

打包 img

url-loader file-loader

代码

const path = require('path')
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const HtmlWebpackPlugin = require('html-webpack-plugin')
const Webpack = require('webpack')


module.exports = {
  mode: 'development',
  // mode: 'production',
  // 这里是打包的入口文件(相对路径)
  entry: './src/index.js',
  output: {
    // 打包结果存放的位置(必须用绝对路劲)
    path: path.resolve(__dirname, 'img'),
    // path: path.resolve('/Users/rainzhao/collect/webpack/demo/1', 'dist'),
    // 打包结果文件名称
    filename: 'index.[hash:8].js',
  },
  module: {
    rules: [
      {
        test:/\.css/,
        use:[MiniCssExtractPlugin.loader,"css-loader",{
          loader: "postcss-loader",
          options: {
            plugins: () => [require('autoprefixer')]
          }
        }]
      },
      {
        test:/\.less$/,
        use:[MiniCssExtractPlugin.loader,"css-loader",{
          loader: "postcss-loader",
          options: {
            plugins: () => [require('autoprefixer')]
          }
        },"less-loader"]
      },
      {
        test: /\.(png|jpg|jpeg|gif)$/,
        use: [{
          loader: 'file-loader',
          options: {
            name: '[name].[hash:8].[ext]',
            publicPath: "./images/",
            outputPath: "images/"
          }
        }]
      },
      {
        test: /\.(html)$/,
        use: {
          loader: 'html-loader',
          options: {
            attrs: ['img:src', 'img:data-src', 'audio:src'],
            minimize: true
          }
        }
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].[chunkhash:8].css",
      chunkFilename: "[id].css"
    }),
    new Webpack.HotModuleReplacementPlugin(),
    new HtmlWebpackPlugin({
      template: './index.html', // 模板文件          
      filename: 'index.html'
    })
  ]

}

webpack 实现代码拆分 splitChunk

代码

const path = require('path')
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin=require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const Webpack = require('webpack')


module.exports = {
  // mode: 'development',
  // mode: 'production',
  // 这里是打包的入口文件(相对路径)
  entry: './src/index.js',
  output: {
    // 打包结果存放的位置(必须用绝对路劲)
    path: path.resolve(__dirname, 'prod'),
    publicPath: 'http://localhost:8000/',
    // path: path.resolve('/Users/rainzhao/collect/webpack/demo/1', 'dist'),
    // 打包结果文件名称
    filename: 'index.[hash:8].js',
  },
  module: {
    rules: [
      {
        test: /src\/\.js$/,
        use: 'babel-loader',
      },
      {
        test:/\.css/,
        use:[MiniCssExtractPlugin.loader,"css-loader",{
          loader: "postcss-loader",
          options: {
            plugins: () => [require('autoprefixer')]
          }
        }]
      },
      {
        test:/\.less$/,
        use:[MiniCssExtractPlugin.loader,"css-loader",{
          loader: "postcss-loader",
          options: {
            plugins: () => [require('autoprefixer')]
          }
        },"less-loader"]
      },
      {
        test: /\.(png|jpg|jpeg|gif)$/,
        use: [{
          loader: 'url-loader',
          options: {
            limit: 1,
            name: '[name].[hash:8].[ext]',
            outputPath: "images/"
          }
        }]
      },
      {
        test: /\.(html)$/,
        use: {
          loader: 'html-loader',
          options: {
            attrs: ['img:src', 'img:data-src', 'audio:src'],
            minimize: true
          }
        }
      }
    ]
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '-',
      name: true,
      cacheGroups: {
        react: {
          test: /react/,
          name: "react",
          priority: 1,
          minChunks: 1,
        }
      }
    },
    runtimeChunk: {
      name: "manifest"
    }
  },
  plugins: [
    new CleanWebpackPlugin('prod/*.*', {
      root: __dirname,
      verbose: true,
      dry: false
    }),
    new CopyWebpackPlugin([{
      from: path.join(__dirname, './favicon.ico'),
      to: path.join(__dirname, 'prod')
    }]),
    new MiniCssExtractPlugin({
      filename: "[name].[chunkhash:8].css",
      chunkFilename: "[id].css"
    }),
    new Webpack.HotModuleReplacementPlugin(),
    new HtmlWebpackPlugin({
      template: './index.html', // 模板文件
      filename: 'index.html'
    })
  ]

}

按需加载

// index.js
const btn = document.querySelector("#btn");
btn.onclick = ()=>{
  // 点击按钮加载 foo
  import(/* webpackChunkName: "foo" */ './foo').then(function(module){
    const foo = module.default;
    console.log(foo);
  })
}

总结

webpack 把你的项目当做一个整体,通过一个给定的入口文件(如:index.js),从这个文件开始找到你的项目的所有依赖文件,使用loaders处理它们,最后打包为一个浏览器可识别的JavaScript文件

使 es6 中不能直接使用的特性,转换了es5中浏览器支持的方法。

sass,less 等 预处理器

模块化,让我们可以把复杂的应用细化为小的组件;

demo 源码

demo 源码传送门