webpack 进阶

341 阅读5分钟
以下内容主要用于webpack4.0版本 
//项目自动设置淘宝源,不用修改全局
touch .npmrc
//定义镜像
npm config set registry https://registry.npm.taobao.org

css样式处理

集成css样式处理: css-loader style-loader 注意版本号

npm install css-loader@5.0.0  style-loader@2.0.0 -D
module: {
    rules: [
        {
            test: /\.css$/,
            use: ["style-loader", "css-loader"],
        },
    ],
},

sass安装

npm i node-sass@4.13.0 sass-loader@8.0.0 -D
rules:[
{
    test: /\.scss$/,
    use: ["style-loader","css-loader","sass-loader"]
}, 

postcss:

相当于babel于JS

  • postcss主要功能只有两个:
  • 第⼀就是把css解析成JS可以操作的抽象语法树AST,
  • 第⼆就是调⽤插件来处理AST并得到结果;所以postcss⼀般都是通过插件来处理css,并不会直接处理 ⽐如:

⾃动补⻬浏览器前缀: autoprefixercss(用于浏览器兼容) cssnano(压缩)

npm i postcss@8.1.4 postcss-loader@4.0.4  autoprefixer@10.0.1 cssnano@4.1.10 -D

browserslist设置3种方式

# 创建与配置postcss.config.js //一定要创建postcss.config.js
module.exports = {
    plugins: [require("autoprefixer")],
};

# 1.配置package.json
"browserslist":["last 2 versions", "> 1%"],
# 2.直接在postcss.config.js⾥配置
module.exports = {
    plugins: [
        require("autoprefixer")({
            overrideBrowserslist: ["last 2 versions", "> 1%"],
        }),
    ],
};
# 3.创建.browserslistrc⽂件
> 1%
last 2 versions
not ie <= 8

#注意 不要在 webpack.config.js 里面使用下面定义,会无效
 plugins: [
    require("autoprefixer")({
        overrideBrowserslist: ["last 2 versions", "> 1%"],
    }),
],

样式文件分离

好处:可以使用cdn的方式并行下载,提供加载速度。

# 安装
npm install mini-css-extract-plugin@1.2.1 -D
# 使⽤
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
    module: {
        rules: [
            {
                test: /\.less$/,
                use: [
                    // 插件需要参与模块解析,须在此设置此项,不再需要style-loader
                    {
                        loader: MiniCssExtractPlugin.loader,
                        options: {
                            hmr: true, // 模块热替换,仅需在开发环境开启
                            // reloadAll: true,
                            // ... 其他配置
                        }
                    },
                    'css-loader',
                    'postcss-loader',
                    'less-loader'
                ],
            },
        ],
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: '[name].css', // 输出⽂件的名字
            // ... 其他配置
        }),
    ]
};

内联css js html

方案1 : raw-loader

 <style>
${require('!raw-loader!less-loader!./product.less')}
 </style>
${ require('raw-loader!./product.html')}
<script>${ require('raw-loader!babel-loader!./product.js')}</script>

方案2 :mini-css + html-inline-css-webpack-plugin

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HTMLInlineCSSWebpackPlugin = require('html-inline-css-webpack-plugin').default;
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
    entry: entry,
    output: {
        path: path.join(__dirname, 'dist'),
        filename: '[name]_[chunkhash:8].js'
    },
    mode: 'development',
    module: {
        rules: [
            {
                test: /.js$/,
                use: [
                    'babel-loader',
                ]
            },
            {
                test: /.css$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader'
                ]
            },
            {
                test: /.less$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    {
                        loader: 'px2rem-loader',
                        options: {
                            remUnit: 75,
                            remPrecision: 8
                        }
                    },
                    'less-loader',
                    {
                        loader: 'postcss-loader',
                        options: {
                            plugins: () => [
                                require('autoprefixer')({
                                    browsers: ['last 2 version', '>1%', 'ios 7']
                                })
                            ]
                        }
                    }
                ]
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: '[name]_[contenthash:8].css'
        }),
        new CleanWebpackPlugin(),
    ].concat(new HtmlWebpackPlugin({
      inlineSource: '.css$', 
      inject: true,
      minify: {
          html5: true,
          collapseWhitespace: true,
          preserveLineBreaks: false,
          minifyCSS: false,
          minifyJS: false,
          removeComments: false
      }
  }))
    .concat(new HTMLInlineCSSWebpackPlugin()),
    stats: 'errors-only'
};

webpack 样式显示几种方式

  1. 使用style-loader js执行插入style样式
  2. 使用mini-css 通过生成实际的xx.css文件同时使用 link方式引入
  3. 使用mini-css + html-inline-css-webpack-plugin (缺点:css必须插在head里面)
  4. 使用raw-loader 直接引入到页面上,真正的内联到html

image.png

图⽚/字体/⽂件处理

  • url - loader
  • file - loader url - loader 和 file - loader 都可以⽤来处理本地的资源⽂件,如图⽚、字体、⾳视频等。功能也是 类似的, 不过 url - loader 可以指定在⽂件⼤⼩⼩于指定的限制时,返回 DataURL ,不会输出真实的 ⽂件,可以减少昂贵的⽹络请求。
注意:
//# 安装
npm install url-loader@4.1.1 file-loader@6.2.0 -D
//# ⼊⼝⽂件
import pic from "./logo.png";
var img = new Image();
img.src = pic;
img.classList.add("logo");
var root = document.getElementById("root");
root.append(img);
//# 使⽤

//字体处理
const path = require("path");
const htmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
    // 必填 webpack执⾏构建⼊⼝
    entry: "./src6/index.js",
    output: {
        filename: "[name][chunkhash:8].js",//利⽤占位符,⽂件名称不要重复
        path: path.resolve(__dirname, "dist6")//输出⽂件到磁盘的⽬录,必须是绝对路径
    },
    module: {
      rules: [
        {
            test: /\.(png|jpg|gif|jpeg|webp|svg|eot|ttf|woff|woff2)$/,
            exclude: /node_modules/,
            use: [
                {
                    loader: 'url-loader', // 仅配置url-loader即可,内部会⾃动调⽤file- loader
                    options: {
                              esModule: false, //此参数非常重要,可以解决 在4.0里面会导致css 中使用url([object Module])的问题
                              limit: 10240, //⼩于此值的⽂件会被转换成DataURL
                              name: '[name]_[hash:6].[ext]', // 设置输出⽂件的名字
                              outputPath: 'assets', // 设置资源输出的⽬录
                            }
                    }
              ]
        }
      ]
    },
    plugins: [
      new htmlWebpackPlugin({
          title: "My App",
          filename: "app.html",
          template: "./src6/index.html"
      }),
  ]
};

css文件夹导致访问图片问题

注意:当导出的样式使用MiniCssExtractPlugin 生成文件存放在css文件夹,图片的路径就需要设置`publicpath`

方案1:调整css-loader

//当把css文件放在css文件夹时候,css配置 需要制定
  plugins: [
    new MiniCssExtractPlugin({
        filename:'css/[name].css'
    }),
  ]
//原来的 
 {
    test: /\.css$/,
    use: [MiniCssExtractPlugin.loader,"css-loader"]
  },
  //调整为 
 {
    test: /\.css$/,
    use: [{
      loader:MiniCssExtractPlugin.loader,
      options:{
        publicPath: '../'
      }
    },"css-loader"]
  },
  //这样所有在css里面的url操作都会自动加上../的访问操作

方案2调整url-loader

   //直接修改 图片生成的路径
   {
    test: /\.(png|jpg|gif|jpeg|webp|svg|eot|ttf|woff|woff2)$/,
    use: [
      {
          loader: 'url-loader', // 仅配置url-loader即可,内部会⾃动调⽤file- loader
          options: {
              esModule: false,//此参数非常重要,可以解决 在4.0里面会导致css 中使用url([object Module])的问题
              limit: 10240, //⼩于此值的⽂件会被转换成DataURL
              name: '[name].[ext]', // 设置输出⽂件的名字
              outputPath: 'assets', // 设置资源输出的⽬录
              publicPath: "../assets"
          }
      }
    ]
  }

字体使用

//css
@font-face {
    font-family: "webfont";
    font-display: swap;
    src: url("webfont.woff2") format("woff2");
}
body {
    background: blue;
    font-family: "webfont" !important;
}

//webpack.config.js
{
    test: /\.(eot|ttf|woff|woff2|svg)$/,
    use: "file-loader"
}

clean-webpack-plugin

  • clean-webpack-plugin:如何做到dist⽬录下某个⽂件或⽬录不被清空: 使⽤配置项:cleanOnceBeforeBuildPatterns
  • 案例:cleanOnceBeforeBuildPatterns: ["/*", "!dll", "!dll/"],
  • !感叹号相当于exclude 排除,意思是清空操作排除dll⽬录,和dll⽬录下所有⽂件。 注意:数组列表⾥的

“*/”是默认值,不可忽略,否则不做清空操作。

//npm install --save-dev clean-webpack-plugin
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
...
plugins: [
        new CleanWebpackPlugin()
    ]
}

HtmlWebpackPlugin

html⻚⾯处理

htmlwebpackplugin会在打包结束后,⾃动⽣成⼀个html⽂件,并把打包⽣成的js模块引⼊到该html 中。

npm install --save-dev html-webpack-plugin@3.2.0

配置:

  • title: ⽤来⽣成⻚⾯的 title 元素
  • filename: 输出的 HTML ⽂件名,默认是 index.html, 也可以直接配置带有⼦⽬录。
  • template: 模板⽂件路径,⽀持加载器,⽐如 html!./ index.html
  • inject: true | 'head' | 'body' | false, 注⼊所有的资源到特定的 template 或者
  • templateContent 中,如果设置为 true 或者 body,所有的 javascript 资源将被放置到 body元素的底部,'head' 将放置到 head 元素中。
  • favicon: 添加特定的 favicon 路径到输出的 HTML ⽂件中。
  • minify: { } | false, 传递 html - minifier 选项给 minify 输出
  • hash: true | false, 如果为 true, 将添加⼀个唯⼀的 webpack 编译 hash 到所有包含的脚本和CSS ⽂件,对于解除 cache 很有⽤。
  • cache: true | false,如果为 true, 这是默认值,仅仅在⽂件修改之后才会发布⽂件。
  • showErrors: true | false, 如果为 true, 这是默认值,错误信息会写⼊到 HTML ⻚⾯中
  • chunks: 允许只添加某些块(⽐如,仅仅 unit test 块)
  • chunksSortMode: 允许控制块在添加到⻚⾯之前的排序⽅式,⽀持的值:'none' | 'default' |
  • { function} -default: 'auto'
  • excludeChunks: 允许跳过某些块,(⽐如,跳过单元测试的块)

案例:


const path = require("path");
const htmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
    ...
    plugins: [
        new htmlWebpackPlugin({
            title: "My App",
            filename: "app.html",
            template: "./src/index.html"
        })
    ]
};
//index.html
< !DOCTYPE html >
    <html lang="en">
        <head>
            <meta charset="UTF-8" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <meta http-equiv="X-UA-Compatible" content="ie=edge" />
            <title><%= htmlWebpackPlugin.options.title %></title>
        </head>
        <body>
            <div id="root"></div> 
        </body>
    </html>

占位符定义文件名

通过 输出摘要的唯一名字实现,文件是否需要重新下载还是缓存。

算法

文件指纹的概念:通过把文件使用hash函数的方式生成摘要。 表示输⼊任意⻓度的数据,都会输出固定⻓度的数据。通过 摘要算法(⽐如MDS和SHA-1)就可以得到该哈希值。

  • 不定⻓输⼊转定⻓摘要
  • 满⾜雪崩效应
  • 单项不可逆 常⻅的哈希算法 MD5 : 输出128bit⻓度的⼆进制串 SHA-1:输出160bit⻓度的⼆进制串 SHA-256: 输出256bit⻓度的⼆进制串 SHA-512: 输出512bit⻓度的⼆进制串
// webpack.config.js
module.exports = {
 // ...
 entry: {
     app: "./src/app.js",
     index: "./src/index.js",
 },
 output: {
     filename: "[name][chunkhash:8].js",
 // ...path
 },
};

SourceMap源码定位

定义:在编译处理的过程中,在⽣成产物代码的同时⽣成产物代码中被转换的部分与源代码中相应部分的映射关系表。

在dev模式中,默认打开

module.exports = {
    devtool: "none" // /none/source-map / cheap-module-eval-source-map / cheap-module-source-map
}

eval:速度最快,使⽤eval包裹模块代码,生成映射关系代码,效率和性能最佳。但是当代码复杂时,提示信息可能不精确。
source-map: 产⽣ .map ⽂件
cheap:有两种作用:一是将错误只定位到行,不定位到列。二是映射业务代码,不映射loader和第三方库等。
module:第三⽅模块,包含loader的sourcemap(⽐如jsx to js ,babel的sourcemap)
inline: 将 .map 作为DataURI嵌⼊,不单独⽣成 .map ⽂件
 
//推荐设置:
//开发环境  development
devtool: 'cheap-module-eval-source-map', 
//生产环境   production
devtool: 'cheap-module-source-map',

WebpackDevServer

提升开发效率的利器 每次改完代码都需要重新打包⼀次,打开浏览器,刷新⼀次,很麻烦, 我们可以安装使⽤

缺点:整个页面刷新

//安装
npm install webpack-dev-server@3.11.0 -D
//配置
//修改下package.json
"scripts": {
    "server": "webpack-dev-server"
},
//在webpack.config.js配置:
devServer: {
    contentBase: "./dist",
        open: true,
            port: 8081
},
//启动
npm run server

//启动服务后,会发现dist⽬录没有了,这是因为devServer把打包后的模块不会放在dist⽬录下,⽽是放到内存中,从⽽提升速度

跨域处理

修改webpack.config.js 设置服务器代理

proxy: {
    "/api": {
        target: "http://localhost:9092"
    }
}

//代理后直接通过api访问 
axios.get("/api/info").then(res => {
    console.log(res);
});

Babel处理ES6

安装配置

Babel是JavaScript编译器,能将ES6代码转换成ES5代码,让我们开发过程中放⼼使⽤JS新特性⽽不⽤担⼼兼容性问题。并且还可以通过插件机制根据需求灵活的扩展。

Babel在执⾏编译的过程中,会从项⽬根⽬录下的.babelrc JSON⽂件中读取配置。没有该⽂件会从 loader的options地⽅读取配置。

测试代码

//index.js
const arr = [new Promise(() => { }), new Promise(() => { })];
arr.map(item => {
    console.log(item);
});
//Ecma2015 -> es6 es12

安装
npm i babel-loader@8.0.5 @babel/core@7.4.4  @babel/preset-env@7.4.4 -D
//npm i babel-loader @babel/core @babel/preset-env -D
//babel - loader是webpack 与 babel的通信桥梁,不会做把es6转成es5的⼯作,这部分⼯作需要⽤到
@babel/preset-env来做
Webpack.config.js
{
    test: /\.js$/,
        exclude: /node_modules/,
            use: {
        loader: "babel-loader",
            options: {
            presets: ["@babel/preset-env"]
        }
    }
}

通过上⾯的⼏步 还不够,默认的Babel只⽀持let等⼀些基础的特性转换,Promise等⼀些还有转换过 来,这时候需要借助@babel/polyfill,把es的新特性都装进来,来弥补低版本浏览器中缺失的特性

@babel/polyfill

1.全局变量的⽅式注⼊进来的。

windows.Promise,它会造成全局对象的污染,并且是所有ployfill的es新特性内容都打包进来。

npm install --save @babel/polyfill@7.4.4

//index.js 顶部

import "@babel/polyfill";

2.按需加载

假如我想我⽤到的es6 +,才会注⼊,没⽤到的不注⼊,从⽽减少打包的体积


options: {
    presets: [
        [
            "@babel/preset-env",
            {
                targets: {
                    edge: "17",
                    firefox: "60",
                    chrome: "67",
                    safari: "11.1"
                },
                corejs: 2,//新版本需要指定核⼼库版本
                useBuiltIns: "usage"//按需注⼊
            }
        ]
    ]
}

useBuiltIns 选项是 babel 7 的新功能,这个选项告诉 babel 如何配置 @babel/polyfill 。 它有三个参数可以使⽤:

1. "entry"

需要在 webpack 的⼊⼝⽂件⾥ import "@babel/polyfill" ⼀次。babel会根据你的使⽤情况导⼊垫⽚,没有使⽤的功能不会被导⼊相应的垫⽚。 会根据 browserslist 替换成浏览器不兼容的所有 polyfill。这里需要指定 core-js 的版本。

如果 "corejs": 3, 则 import '@babel/polyfill' 需要改成

import 'core-js/stable'; 
import 'regenerator-runtime/runtime';

建议使用corejs2 ,3打包体积比较大

2. "usage"(推荐)

不需要 import ,全⾃动检测,但是要安装 @babel/polyfill 。 usage 会根据配置的浏览器兼容,以及你代码中用到的 API 来进行 polyfill,实现了按需添加。

3. false

如果你 import "@babel/polyfill" ,所有内容都导入,体积超大。(不推荐)

扩展:

babelrc⽂件: 新建.babelrc⽂件,把options部分移⼊到该⽂件中,就可以了

//.babelrc
{
    presets: [
        [
            "@babel/preset-env",
            {
                targets: {
                    edge: "17",
                    firefox: "60",
                    chrome: "67",
                    safari: "11.1"
                },
                corejs: 2, //新版本需要指定核⼼库版本
                useBuiltIns: "usage" //按需注⼊
            }
        ]
    ]
}
//webpack.config.js
{
    test: /\.js$/,
        exclude: /node_modules/,
            loader: "babel-loader"
}

扩展知识core-js

core-js 是 babel-polyfill 的底层依赖,用 ES3 实现了大部分的 ES2017 原生标准库,同时还要严格遵循规范。

React集成

//安装引擎 npm i react react-dom --save //转化器 npm install --save-dev @babel/preset-react

//index.js
import React, { Component } from "react";
import ReactDom from "react-dom";
class App extends Component {
    render() {
        return <div>hello world</div>;
    }
}
ReactDom.render(<App />, document.getElementById("app"));

//index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body> 
    <div id="app">
        xxxxx
    </div>
</body>
</html>

//.babelrc
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "chrome": "66",
          "edge": "16"
        },
        "corejs": 2,
        "useBuiltIns": "usage"
      }
    ],
    "@babel/preset-react"
  ]
}

参数传入webpack

把option对象 改成 方法 支持参数传入

缺点:跨平台系统 可能有环境兼容问题

//webpack.config.js 
module.exports = (env) => {
    if(env.myParam == true) {
        ...
    }
}

//package.json
{
    "dev" : "webpack --env.myParam"
}

参考

github.com/mjsong07/we…