Webpack4 进阶 [ 核心配置与实战 ]

350 阅读8分钟

前言

前边有写过关于 webpack 的核心配置概念与基础实践,今天主要分享 webpack 针对各种情况的配置与实战

webpack 配置概念

webpack 基础实践

这里需要知道一个前提 loader 和 plugins 的作用是什么 ?

  • loader: 文件加载器,由于webpack是用 node 编写,所以只能识别 js 文件,而 loader 的作用就是文件识别与转换
  • plugins: 插件机制,在webpack执行过程中会广播一系列事件,plugin 会监听这些事件并通过 webpack Api 对输出文件做对应的处理

ok, 基于一个前章的基础实践demo开始延申,如需从0开始,可以查看上述链接基础实践

目录结构

webpack // 工程目录 
    dist // 构建生成目录 
        bundle.js 
    index.html 
    main.js 
    utils.js 
    webpack.config.js 
    package.json 
    node_modules
    main.css

webpack.config.js


const path = require('path');
module.exports = {
  entry: './main.js', // 入口文件
  output: {
    // 把所有依赖的模块合并输出到一个 bundle.js 文件
    filename: 'bundle[hash:6].js',
    // 输出文件都放到 dist 目录下
    // __dirname 指向当前目录
    path: path.resolve(__dirname, './dist'),
  },
  //mode:"production",
  devServer:{      // 本地服务
    port:"1234",   // 启动端口
    progress:true, // 进度条
    open:true      // 自动打开浏览器
  }
};

html 插件 html-webpack-plugin

就单页SPA来讲,每次打包我们都会生成一个 dist 目录,目录里边一般含有打包的 bundle.js 和一个入口文件 index.html ,那么 bundle.js 我们知道是通过 output 出口配置生成的,index.html 是哪里来的 ?

ok, 其实它是通过 html-webpack-plugin 插件生成的,主要原理是基于一个模板文件,创建一个新的文件并且自动引入 bundle.js 和其它打包后的 css文件,具体是这样使用的

安装 html-webpack-plugin

cnpm install html-webpack-plugin -D

webpack.config.js 配置


const path = require('path');
// 导入 html-webpack-plugin 插件
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: './main.js', 
  output: {
    filename: 'bundle[hash:6].js',
    path: path.resolve(__dirname, './dist'),
  },
  devServer:{      
    port:"1234",   
    progress:true, 
    open:true      
  },
  plugins: [
   // 使用插件方式
    new HtmlWebpackPlugin({
        template: "./index.html",        // 模板html文件
        filename: "index.html",          // 打包后新建的html文件名
        minify: {                        // 一些优化配置
            removeAttributeQuotes: true, // 去除引号
            removeEmptyAttributes: true, // 去除空属性
            collapseWhitespace: true     // 去除空格
            // 当然还有很多,可查阅文档
        }
    })
  ]
};

通过这样的一个插件呢,就可以做到,每次打包在 dist 目录生成一个新的入口文件,并且自动引入打包后的 js 和 css

样式处理

实际在工作中,我们都会写一些样式,除了用 css,也还会用一些 less,sass 等预处理器,浏览器本身是识别不了的,其实都是 webpack 的功劳,用一些 loader 把不能识别的 less 或者 sass 进行转换,变成 css,再用 style 标签插入到 html 中,就可以了,看一看它是怎么做的 ?

安装 style-loader css-loader less less-loader

cnpm install style-loader css-loader -D
cnpm install less less-loader -D

webpack.config.js 配置


const path = require('path');
// 导入 html-webpack-plugin 插件
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: './main.js', 
  output: {
    filename: 'bundle[hash:6].js',
    path: path.resolve(__dirname, './dist'),
  },
  devServer:{      
    port:"1234",   
    progress:true, 
    open:true      
  },
  module: { // loader 主要使用的地方
    rules: [
        // 配置打包css文件的loader,use定义使用到的loader,注意顺序
        // style-loader 必须放在 css-loader 前面,因为loader的执行顺序是从右至左
        { 
            test: /\.css$/,  // 匹配后缀为 .css 的文件
            use: ['style-loader', 'css-loader'] 
        }
    ]
  }
  plugins: [
   // 使用插件方式
    new HtmlWebpackPlugin({
        template: "./index.html",        // 模板html文件
        filename: "index.html",          // 打包后新建的html文件名
        minify: {                        // 一些优化配置
            removeAttributeQuotes: true, // 去除引号
            removeEmptyAttributes: true, // 去除空属性
            collapseWhitespace: true     // 去除空格
            // 当然还有很多,可查阅文档
        }
    })
  ]
};

rule的另一种配置, 可以写成对象的形式,因为还支持一些其它的配置项

{ 
    test: /\.(css|less)$/,  // 匹配后缀为 .less 或者 .css 结尾的文件
    use: [
        {
            loader: "style-loader",
            options: {
                // 其它相关设置
            }
        },
        'css-loader', // 解析@import路径
        'less-loader' // 把less解析为css
    ]
},

来解释一下如上配置,是怎么进行 css 样式处理的

  • 由于 loader 的执行顺序是从右向左,所以这里是先执行 less-loader, 将 less 转换成 css
  • 然后再由 css-loader 解析,并识别 @import 转递给 style-loader
  • 最后 style-loader 会把解析后的 css 装进 style 标签,并放入 html 的 head 中
  • ok 浏览器可以识别,并渲染了

那在实际开发中,肯定不可能都写成 style 标签嵌入 head 中,也不太雅观,所以我们可以通过插件机制来将 css分离出来,单独打包并通过 link 标签引入

Css 抽离 mini-css-extract-plugin

安装 mini-css-extract-plugin

npm install mini-css-extract-plugin -D

webpack.config.js 配置


const path = require('path');
// 导入 html-webpack-plugin 插件
const HtmlWebpackPlugin = require("html-webpack-plugin");
// css 抽离
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  entry: './main.js', 
  output: {
    filename: 'bundle[hash:6].js',
    path: path.resolve(__dirname, './dist'),
  },
  devServer:{      
    port:"1234",   
    progress:true, 
    open:true      
  },
  module: { // loader 主要使用的地方
    rules: [
        // 配置打包css文件的loader,use定义使用到的loader,注意顺序
        // style-loader 必须放在 css-loader 前面,因为loader的执行顺序是从右至左
        { 
            test: /\.css$/,  // 匹配后缀为 .css 的文件
            use: ['style-loader', 'css-loader'] 
        }
    ]
  }
  plugins: [
   // 使用插件方式
   new HtmlWebpackPlugin({
        template: "./index.html",        // 模板html文件
        filename: "index.html",          // 打包后新建的html文件名
        minify: {                        // 一些优化配置
            removeAttributeQuotes: true, // 去除引号
            removeEmptyAttributes: true, // 去除空属性
            collapseWhitespace: true     // 去除空格
            // 当然还有很多,可查阅文档
        }
    }),
    // 添加css抽离插件
    new MiniCssExtractPlugin({
        filename: "main.css", // css抽离出来的文件名,可配置路径,如“css/main.css”
        // 还有很多配置项,请查看文档,这里只列举
    })
    
  ]
};

当然,我们也可以指定文件去进行抽离, MiniCssExtractPlugin 插件的另一种用法,例如,我们想抽离 css 或者 less 就可以在 loader 配置规则里,使用 MiniCssExtractPlugin 插件,如下:

注意位置,一定是放在最上边,没有 style-loader , 意思是当 css 转换完成后,通过 MiniCssExtractPlugin 插件抽离出来,在页面中用 link 标签引入

    // css 示例
    { 
        test: /\.css$/, 
        use: [
            MiniCssExtractPlugin.loader,
            'css-loader', // 解析@import路径
        ]
    },
    // less 示例
    { 
        test: /\.less$/, 
        use: [
            MiniCssExtractPlugin.loader,
            'css-loader', // 解析@import路径
            'less-loader' // 把less解析为css
        ]
    },
            

扩展

  • 可以通过 postcss-loader 给 css 样式加上前缀,来满足浏览器兼容
  • 通过插件 optimize-css-assets-webpack-plugin 压缩 css , 同时需要使用 uglify-js-plugin 来压缩 js

转换ES6置ES5

这里需要使用到 bable, 主要是用来转化 js 语法的

安装

// babel-loader babel的加载器
// @babel/core babel 的核心模块
// @babel/preset-env 将 es6 转化成 es5 
npm install  babel-loader @babel/core @babel/preset-env -D

webpack.config.js 配置


const path = require('path');
// 导入 html-webpack-plugin 插件
const HtmlWebpackPlugin = require("html-webpack-plugin");
// css 抽离
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  entry: './main.js', 
  output: {
    filename: 'bundle[hash:6].js',
    path: path.resolve(__dirname, './dist'),
  },
  devServer:{      
    port:"1234",   
    progress:true, 
    open:true      
  },
  module: { // loader 主要使用的地方
    rules: [
        // 配置打包css文件的loader,use定义使用到的loader,注意顺序
        // style-loader 必须放在 css-loader 前面,因为loader的执行顺序是从右至左
        { 
            test: /\.css$/,  // 匹配后缀为 .css 的文件
            use: ['style-loader', 'css-loader'] 
        },
        
        // js => es6 转化  es5
        {
            test:/\.js$/, // 匹配后缀为 .js 的文件
            use:{ // 这里采用对象的形式编写
                loader: 'babel-loader',
                options: {
                  presets: ['@babel/preset-env'], // es6 转化 es5
                  plugins: [  // 也可以给单个loader配置 plugins 插件,详见文档
                      // 转化class类的写法,识别某些高级语法
                      ["@babel/plugin-proposal-class-properties", { "loose" : true }]
                  ]
                }
              }
        }
        
    ]
  }
  plugins: [
   // 使用插件方式
   new HtmlWebpackPlugin({
        template: "./index.html",        // 模板html文件
        filename: "index.html",          // 打包后新建的html文件名
        minify: {                        // 一些优化配置
            removeAttributeQuotes: true, // 去除引号
            removeEmptyAttributes: true, // 去除空属性
            collapseWhitespace: true     // 去除空格
            // 当然还有很多,可查阅文档
        }
    }),
    // 添加css抽离插件
    new MiniCssExtractPlugin({
        filename: "main.css", // css抽离出来的文件名,可配置路径,如“css/main.css”
        // 还有很多配置项,请查看文档,这里只列举
    })
    
  ]
};

图片处理

一般我们在开发中写图片的方式有三种情况, 那么根据这三种情况,webpack 也有相应的方式做处理

  • Js 中创建图片 file-loader 或者 url-loader
  • css 中引入图片 默认css loader 会转化成 require 的形式
  • html 中使用 img 标签 html-withimg-loader

安装

// file-loader 会将 img 图片生成在打包 build 目录下
// url-loader 
npm install  babel-loader file-loader url-loader -D
npm install  babel-loader html-withimg-loader -D

webpack.config.js 配置


const path = require('path');
// 导入 html-webpack-plugin 插件
const HtmlWebpackPlugin = require("html-webpack-plugin");
// css 抽离
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  entry: './main.js', 
  output: {
    filename: 'bundle[hash:6].js',
    path: path.resolve(__dirname, './dist'),
  },
  devServer:{      
    port:"1234",   
    progress:true, 
    open:true      
  },
  module: { // loader 主要使用的地方
    rules: [
        // 配置打包css文件的loader,use定义使用到的loader,注意顺序
        // style-loader 必须放在 css-loader 前面,因为loader的执行顺序是从右至左
        { 
            test: /\.css$/,  // 匹配后缀为 .css 的文件
            use: ['style-loader', 'css-loader'] 
        },
        
        // js => es6 转化  es5
        {
            test:/\.js$/, // 匹配后缀为 .js 的文件
            use:{ // 这里采用对象的形式编写
                loader: 'babel-loader',
                options: {
                  presets: ['@babel/preset-env'], // es6 转化 es5
                  plugins: [  // 也可以给单个loader配置 plugins 插件,详见文档
                      // 转化class类的写法,识别某些高级语法
                      ["@babel/plugin-proposal-class-properties", { "loose" : true }]
                  ]
                }
              }
        },
        
        
        // 图片loader处理
        { 
            test: /\.(jpg|png|gif)$/, // 匹配后缀 jpg,png,gif 为后缀的文件
            use: {
                loader: 'file-loader', // 用于 js 创建图片
                options:{
                    esModule: false, // 压缩图片路径
                    outputPath: '/img/',  // 打包存放在 img 文件夹下,可自定义,url-loader 同理
                    publicPath: 'htpp://localhost' // 路径前添加域名,也可以在 outout 出口公共配置
                }
            },
            /*  url-loader 替代 file-loader 
            { 
                test: /\.(jpg|png|gif)$/, // 匹配后缀 jpg,png,gif 为后缀的文件
                use: {
                    loader: 'url-loader', // 图片不一定需要产出,可以给一个大小限制, 使用 base64 
                    options: {
                        limit:200*1024 // 限制 200k 的使用 base64 格式 
                        // 其它配置参考文档
                    }
            },
            */
            { 
                test: /\.html$/, 匹配后缀 html 的文件
                use: 'html-withimg-loader'  // 用于 html 里 img 标签 src 的路径识别
            },
        
    ]
  }
  plugins: [
   // 使用插件方式
   new HtmlWebpackPlugin({
        template: "./index.html",        // 模板html文件
        filename: "index.html",          // 打包后新建的html文件名
        minify: {                        // 一些优化配置
            removeAttributeQuotes: true, // 去除引号
            removeEmptyAttributes: true, // 去除空属性
            collapseWhitespace: true     // 去除空格
            // 当然还有很多,可查阅文档
        }
    }),
    // 添加css抽离插件
    new MiniCssExtractPlugin({
        filename: "main.css", // css抽离出来的文件名,可配置路径,如“css/main.css”
        // 还有很多配置项,请查看文档,这里只列举
    })
    
  ]
};

配置 source-map

source-map 是什么 ? 是源码映射,因为我们打包后,发布,一旦出现了问题,这个时候,代码都被混淆和压缩过,很难定位问题代码的具体位置,而 source-map 就是来解决它的


module.exports = {
    entry:  { /*省略*/},
    // 对应多出口
    output: { /*省略*/}
    // 1。源码映射,单独生成一个sourcemap文件,出错时会直接提示报错行
    devtool: 'source-map', // 大而全的设置
    
    // 2。源码映射,不会产生单独的文件,单会直接显示错误的行列
    // devtool: 'eval-source-map',
    
    // 3。不会产生列,但是会生成一个单独的映射文件
    // devtool: 'cheap-module-source-map', // 产生后可以保留起来
    
    // 4。不会产生文件,集成再打包后的文件中,也不会产生列
    // devtool: 'cheap-module-source-map', // 产生后可以保留起来
}

watch 实时打包

这个功能其实并不是太实用,简单介绍一下吧

安装 clean-webpack-plugin,每次实时打包,保证 dist 目录是最新的打包代码

cnpm install clean-webpack-plugin -D // 打包前清除dist目录
// 扩展两个插件
cnpm install copy-webpack-plugin -D  // copy 目录中的文件到指定目录
bannerPlugin // 版权声明插件,webpack 内置

module.exports = {
    entry:  { /*省略*/},
    // 对应多出口
    output: { /*省略*/}
    // 1。源码映射,单独生成一个sourcemap文件,出错时会直接提示报错行
    devtool: 'source-map', // 大而全的设置
    // 开启监控,实时打包设置
    watch: true,
    watchOptions:{ // 设置监控选项
        poll: 1000, // 每秒查询1000次
        aggregateTimeout: 500, // 防抖,ctrl+s后多久开始打包
        ignored: /node_modules/, // 忽略不需要监控的文件夹
    },
    plugins:[
        new CleanWebpackPlugin(), // 先删除dist目录再打包,不用填参数
    ]
}

webpack 如何处理跨域

跨域应该是前端中比较常见的问题了,一般可能都给到了后端去处理,那么在 webpack 中也是有对应的解决方案,代理


export.modules{
    entry:  { /*省略*/},
    // 对应多出口
    output: { /*省略*/}
    // webpack.config.js
    devServer{
        // 配置代理
        proxy:{
            '/api': { // 匹配请求接口url包含 /api 的请求,进行拦截
                target: 'http://localhost:3000', // 转发配置代理指向域名
                pathRewrite: {'^/api':''}, // 将请求路径中开头/api替换为空
                changeOrigin: true,     // target是域名的话,需要这个参数,
                secure: false,          // 设置支持https协议的代理
            }
        }
    }
}

pathRewrite 这个配置项需要注意,因为在实际开发中,后端接口可能不是那么如意,并不可能每个接口都带有 /api 这样写的目的是,我们前端在请求时,都带上 /api, 然后经过webpack proxy 代理时,匹配上,再替换掉。

resolve 解析第三方包

这个配置很有意思,对我们开发是一神器,直接关系到我们针对不同层级组件导入文件,图片等资源的路径配置


export.modules{
    entry:  { /*省略*/},
    // 对应多出口
    output: { /*省略*/}
    // 解析第三方包
    resolve:{ 
        // 解析文件搜索的目录
        modules:[path.resolve('node_modules')],
        
        // 常常在工作中这样写 import A form "../a"
        extensions:['.js','.css','.json','.vue'], // 限定扩展名,按序依次解析
        
        // 设置别名,在 index.js 引入时可直接采用 import 'bootstrap'
        // 在项目中,引入 components 目录下的文件时,可简写为 improt X from "@components/xxx"
        alias:{ 
            bootstrap:'bootstrap/dist/css/bootstrap.css',
            '@Components': path.resolve(__dirname, 'src/components/'),
        },
        // 其它参考文档
    },
}

打包多页 [ 多入口,多出口 ]

其实打包多页,比较容易理解,就是多个入口,对应多个出口,这里做一个简单的例子,便于大家理解


// 多入口配置
entry: {
    home: './src/index.js',
    other: './src/other.js'
},

// 对应多出口配置
output: {
    // 多出口设置, [name]代表多入口变量名home,other,逐一打包
    filename: '[name].js', // name 是内置变量,指向 entry 入口配置对应 key 
    
    // path: path.resolve(__dirname, 'dist'),
    path: path.resolve(__dirname, 'dist'),
    
    // publicPath: 'http://localhost'
},
    
// html-webpack-plugin 对应配置生成多个文件
plugins: [
    new HtmlWebpackPlugin({
        template: "./index.html",
        filename: "index.html",
        chunks: ['home'], // 多页面打包设置,对应入口js
    }),
    // 多页面打包,多个new HtmlWebpackPlugin
    new HtmlWebpackPlugin({
        template: "./other.html",
        filename: "other.html",
        chunks: ['other'], // 也可以同时引入多个 js 这样  ['other', 'home']
    }),
]
    

ok 到这里,暂告一段落,列举了我们在开发中经常用到的各种配置以及loader和插件,紧接着我会再写一篇关于 webpack 内置优化的文章

小小鼓励,大大成长,欢迎点赞