天天看点

webpack从入门及实战(二)webpack核心概念

Loaders

说到webpack,自然离不开他的一大堆loaders,没有loaders的webpack只能打包js文件,得益于众多的loaders,我们可以打包各种类型的文件。

loaders实际上可以理解为一种打包方案,webpack本身碰到除了js文件之外的其他类型文件时,就不知道该如何打包了,然后打包就会失败,而loaders则是对应某种文件类型的文件一种单独的打包方案,通过config配置了loaders之后,webpack就可以打包该类型的文件了

打包图片

图片可以使用file-loader来进行打包,在项目目录执行npm i file-loader -D之后进行一些简单的配置就可以了

const path = require("path");

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "myDist"),
  },
  module: {
    rules: [
      {	
      	// 要打包的文件类型
        test: /\.(png|jpg|gif)$/,
        use: [
          {	
          	// 使用file-loader打包
            loader: "file-loader",
            options: {
              // 生成的文件名是 原文件名_哈希值.原文件扩展名
              name:'[name]_[hash].[ext]',
              // 输出目录是在打包目录下的images文件夹下
              outputPath:'images/'
            },
          },
        ],
      },
    ],
  },
};
           

也可以使用url-loader打包,url-loader会多一个limit配置项,低于该大小的文件会直接被转换成base64,不会再被打包

const path = require("path");

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "myDist"),
  },
  module: {
    rules: [
      {	
      	// 要打包的文件类型
        test: /\.(png|jpg|gif)$/,
        use: [
          {	
          	// 使用file-loader打包
            loader: "file-loader",
            options: {
              // 生成的文件名是 原文件名_哈希值.原文件扩展名
              name:'[name]_[hash].[ext]',
              // 输出目录是在打包目录下的images文件夹下
              outputPath:'images/',
              // 单位是字节,这里代表大于10kb的文件正常打包,小于10kb的文件转成base64
              limit:10240
            },
          },
        ],
      },
    ],
  },
};
           

引入2张图片作为测试

// index.js
import sayHi from './sayHi'

// 小的图片大小为5kb
import smallImg from './images/small.png'
// 大的图片大小为15kb
import bigImg from './images/big.png'

// 将图片挂载至页面
const root = document.getElementById('root');
const smallImage = new Image()
smallImage.src = smallImg
const bigImage = new Image()
bigImage.src = bigImg
root.append(smallImage)
root.append(bigImage)

sayHi()
           

可以看到打包后的images文件夹下只有1张大的图片

webpack从入门及实战(二)webpack核心概念

但是页面上2张图片均可以正常显示

webpack从入门及实战(二)webpack核心概念

打包样式

普通的css需要通过style-loader和css-loader来打包

const path = require("path");

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "myDist"),
  },
  module: {
    rules: [
      {
        test:/\.css$/,
        use:[
          // style-loader将样式挂载至dom节点
          'style-loader',
          // css-loader将css文件整理为字符串
          'css-loader'
        ]
      }
    ],
  },
};
           

css预编译语言需要额外使用对应语言的loader来多做一次解析,这里以less为示例

const path = require("path");

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "myDist"),
  },
  module: {
    rules: [
      {
        test:/\.less$/,
        use:[
          'style-loader',
          {
            loader:'css-loader',
            options:{
              // 如果我们引入的less文件里通过@import引入了其他的less文件,那引入的文件默认会直接进入css-loader导致出错
              // 这里是配置在进入css-loader之前要经过几个loader,这里前面只有1个less-loader,故配置为1
              importLoaders: 1,
            }
          },
          'less-loader'
        ]
      }
    ],
  },
};
           

webpack打包的样式默认是全局通用的,这也就导致会出现样式穿透的问题,我们可以通过modules配置来解决

const path = require("path");

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "myDist"),
  },
  module: {
    rules: [
      {
        test:/\.less$/,
        use:[
          'style-loader',
          {
            loader:'css-loader',
            options:{
              importLoaders: 1,
              // 开启模块化样式
              modules:true
            }
          },
          'less-loader'
        ]
      }
    ],
  },
};
           

引入样式时候就需要发生一些改变

// 非模块化引用
import './index.less'
// 模块化引用
import style from './index.less'
// 使用模块化之后的样式
el.className = style.xxx
           

还有诸多其他各种类型的loader,可以让我们完成各种各样的打包需求

附上文档地址

在需要打包不同的文件时,查阅文档即可

Plugins

plugins(插件)可以在webpack运行到某些时刻时候,帮你做一些事情,比如clean-webpack-plugin可以在打包开始前帮我们清理掉上一次打包后的文件,而html-webpack-plugin则可以在打包后自动生成一个引用了打包完成的js文件的html文件,并且支持指定模板

const path = require("path");
// 引入插件
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "myDist"),
  },
  module: {
  	...
  },
  // 使用插件
  plugins: [
    new CleanWebpackPlugin(), 
    
    new HtmlWebpackPlugin({
      // 自定义title
      title:'html模板',
      // 自定义文件名
      filename:'index.html',
      // 自定义使用模板的路径
      template:'src/public/index.html'
    }),
  ],
};

           

devServer

webpack-dev-server 提供了一个简单的 web 服务器,并且能够实时重新加载,安装后在webpack.config中做简单的配置即可使用

const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/index.js",
  devServer:{
  	// 告诉devServer在哪里查找文件
    contentBase: './myDist',
    // 启动后自动浏览器打开
    open:true,
    // 端口
    port:4396
  },
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "myDist"),
  },
  ...
};
           

但是这里有一个坑,我用的是webpack5.x版本,运行后报错了

webpack从入门及实战(二)webpack核心概念

查阅了文档的各种配置依旧没有搞清楚问题所在,遂求助百度

原来是版本的问题

webpack、webpack-cli、webpack-dev-server是需要版本匹配的

找到了一个可以运行的版本

“webpack”: “^4.43.0”,

“webpack-cli”: “^3.3.12”,

“webpack-dev-server”: “^3.11.0”

安装代码

更新版本之后,dev-server就可以正常使用了

但不得不说webpack的文档实在是难以恭维…

模块热替换(HMR)

模块热替换(Hot Module Replacement 或 HMR)是 webpack 提供的最有用的功能之一,它允许在运行时更新各种模块,而无需进行完全刷新。

现在我们的dev-server在运行的时候,更改代码虽然效果会更新,但是这个更新是直接刷新页面来更新的,而使用了HMR之后,就可以做到不刷新页面直接更新了

功能的启用很简单,插件也已经内置在webpack内部无需额外安装

const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
// 引入webpack
const webpack = require("webpack")

module.exports = {
  entry: "./src/index.js",
  devServer:{
    contentBase: './myDist',
    open:true,
    port:4396,
    // 启用模块热更新
    hot:true
  },
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "myDist"),
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title:'html模板',
      filename:'index.html',
      template:'src/public/index.html'
    }),
    // 使用插件
    new webpack.HotModuleReplacementPlugin()
  ],
};
           

这样就可以实现HMR了,接下来修改样式部分的代码,页面可以做到不刷新直接更新,但js部分的代码还是会刷新的,如果想做到js代码不刷新,则需要通过HMR的api accept来手动配置相应的逻辑

首先要在配置文件里加多一个hotOnly配置

const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
// 引入webpack
const webpack = require("webpack")

module.exports = {
  entry: "./src/index.js",
  devServer:{
    contentBase: './myDist',
    open:true,
    port:4396,
    // 启用模块热更新
    hot:true,
    // 只使用热更新来更新页面,不会自动刷新页面来更新
    hotOnly:true
  },
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "myDist"),
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title:'html模板',
      filename:'index.html',
      template:'src/public/index.html'
    }),
    // 使用插件
    new webpack.HotModuleReplacementPlugin()
  ],
};
           

然后在引用了其他模块的代码里加上对应处理的逻辑

这里sayHi添加了一个id为hi的dom节点

// index.js
import sayHi from "./sayHi";

if (module.hot) {
  module.hot.accept(
    "./sayHi", // 监听具体哪个模块的改变
    function () {
      //手动的删除原来saiHi添加的dom节点,然后重新执行saiHi方法
      console.log('这样就可以不刷新页面重新加载js文件了')
      document.getElementById('root').removeChild(document.getElementById("hi"));
      sayHi();
    } // 用于在模块更新后触发的函数
  );
}
sayHi();
           

但是如果引入的模块比较复杂,这里的配置也会随之复杂很多,所有具体的情况还是要看业务的需求,刷新一下页面其实也问题不大…

Babel

通常我们会配置babel来帮我们将es6的语法转换成es5的语法,来避免一些奇奇怪怪的问题(ie说的就是你)

首先需要安装babel以及它的loader

npm install --save-dev babel-loader @babel/core @babel/preset-env
           

在index.js里引入babel/polyfill

// 注入低版本浏览器不支持的一些es6的新方法,诸如promise,map等
import "@babel/polyfill"
           

然后在loader里面进行简单的配置即可

module:{
  rules:[
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
        options: {
          presets: ["@babel/preset-env"],
        },
      },
  ]
}
           

这时候再进行打包,就可以看到打包后代码里es6的语法已经转换成es5的语法了

也可以做更多的一些适配

module:{
  rules:[
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
        options: {
          presets: [["@babel/preset-env",{
            // 只将用到的es6语法整合进去,没有用到的不整合,减少打包后的大小
            useBuiltIns:'usage'
          }]],
        },
      },
  ]
}
           

这样配置之后就可以把引入的babel/polyfill去掉了,他会在需要的时候被自动引入

更多的配置可以参考babel的文档

SourceMap

SourceMap是一种映射关系,可以帮助我们在代码出现错误的时候定位到错误的位置,他的配置对应的是config中的devtool项

先将它设置为none

const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/index.js",
  devtool:false,
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "myDist"),
  },
  ...
};
           
// index.js
// 在代码里添加一句错误的语句
console.llog('错啦')
           

打包是可以正常执行的,但是我们打开页面后控制台会报错

webpack从入门及实战(二)webpack核心概念

点击查看source后会发现对应的地方是打包之后的代码位置,而不是我们源代码的位置,这对于我们定位错误没有太大帮助,而想要让他定位到我们源代码的位置,就需要配置SourceMap

webpack从入门及实战(二)webpack核心概念
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/index.js",
  devtool:'source-map',
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "myDist"),
  },
  ...
};
           

更改devtool配置再次打包后,点击查看source就会帮我们定位到源码的位置了,打包的文件也会多一个map文件

webpack从入门及实战(二)webpack核心概念

官方的source-map有很多的配置

inline代表不生成map文件,将map文件以base64格式打包至js文件内

cheap代表是否定位错误出现在代码的第几行和第几个字符,带上cheap代表不定位出现在第几个字符,只定位第几行

module代表是否定位一些模块的错误

eval代表以js的eval形式来执行代码,不过这种方式在代码量较大的环境下可能无法准确定位错误

开启SourceMap会造成性能方面的影响,所以通常我们的做法是在开发环境下开启source-map,正式上线时候关闭

我个人会在开发环境下使用’cheap-module-eval-source-map’这种方式提示的错误比较全面,同时打包的速度也比较快

线上环境通常是直接设置为false不开启,如果出了问题需要迅速定位错误的话,使用’cheap-module-source-map’可以帮助我们能准确定位错误

webpack从入门及实战(二)webpack核心概念

关于webpack一些核心的理念基本已经介绍完毕,后续的博文会介绍webpack进阶的一些高级概念