webpack入门--通过实践带你学习webpack

3,886 阅读7分钟

image.jpg

webpack

一直想找个时间总结一下webpack,今天,它来了

webpack是什么?

官方定义:
webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。

关于webpack的诞生

webpack的作者——Tobias Koppers(大家可以去他的github主页瞄一眼,是一位相当高产的大佬),他目前专注于 JavaScript 和开源项目。

image.png

Koppers为什么要写webpack呢?
Koppers最初是一个java开发者,java中有一个功能是GWT(Google Web Toolkit),让Java程序员能用Java编写客户端应用。GWT有一个功能是代码拆分(code splitting),这个功能可以延迟加载不常用的代码,对于要保持初始加载速度的大型应用,这个功能非常重要。Koppers对这个研究了很长时间,但是他没发现JavaScript有哪个开源工具(2012年)具备这个功能,于是就想自己写一个这样的工具,也就是webpack。在写这个工具之前,Koppers给当时的一个前端打包工具modules-webmake 提了issue,希望能加入code splitting的能力。这是2012提出的,到现在这个issue还是open的,又兴趣的可以看看他们的回复:issue
不过最终两人的讨论还是没能让modules-webmake实现代码分割,于是,这位大神就fock了一份modules-webmake,开源了了一个仓库,起名叫webpack,然后自己实现了代码分割。2012年3月10日,webpack诞生了!!
image.png

想了解更多的话,可以点这里-->webpack诞生记我为什么开发webpack

webpack基本使用

webpack的核心概念

  • 入口(entry):打包的入口
  • 输出(output):打包的出口
  • loader:loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)
  • 插件(plugins):在webpack构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要做的事情

这个此处不做详细介绍了,大家可以到官网上先对这4个核心概念做一个基本理解,接下来我们一起通过实践来真正了解它们。

项目初始化

注:此实践项目基于webpack版本为

"webpack": "^4.43.0",
"webpack-cli": "^3.3.12"
  • 新建一个文件夹webpack-study
  • 执行npm init -y 初始化项目
  • 安装webpack相关工具
npm install webpack webpack-cli -D
  • 新建文件src/index.js,并写下以下代码。
const str = 'Hello,I am a handsome boy'
setTimeout(() => {
    console.log(str)
}, 1000);

此时项目已初始化完成,接下来执行命令npx webpack --mode=development

小提示:默认是production模式,代码会被极致压缩为一行,为了方便分析,这里显示声明mode为development模式。另外npx是一个用于执行可执行文件的命令,安装npm后会自动安装npx。


然后你会发现此时出现了一个dist/main.js文件,这就说明我们的打包成功了。

......
({
  "./src/index.js":
  (function(module, exports) {
  eval("const str = 'Hello,I am a handsome boy'\nconsole.log(str)\n\n//# sourceURL=webpack:///./src/index.js?");
  })
});

webpack默认入口为src/index.js,出口是dist/main.js,更多的默认配置可以在node_modules/webpack/lib/WebpackOptionsDefaulter.js查看。接下来我们开始对webpack做一些简单的配置,逐渐理解四个核心概念的意义

配置mode

webpack默认mode是production,开发模式下,我们需要设置为development,除了在命令行中显示声明外,也可以在配置中进行配置

  • 新建webpack.config.js
  • 在该文件中填充以下代码
module.exports = {
    mode: "development"
}

mode 配置项,告知 webpack 使用相应模式的内置优化。
mode 配置项,支持以下两个配置:

development 将 process.env.NODE_ENV 的值设置为 development,启用 NamedChunksPlugin 和 NamedModulesPlugin
production 将 process.env.NODE_ENV 的值设置为 production,启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin

现在,我们直接使用 npx webpack 进行编译即可,该命令会自动寻找webpack.config.js,按照其中的配置进行打包。

入口配置

入口的默认配置是 /src/index.js,如果我们需要改变入口需要配置entry字段。
entry 的值可以是一个字符串,一个数组或是一个对象。
如果是单个入口,可以这样配置

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

或者

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

如果是多个入口,将entry配置成数组

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

如果是多页面配置,待会儿会讲到。

出口配置

出口指的是打包文件的出口,基本配置如下

const path = require('path');
module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'), //必须是绝对路径
        filename: 'bundle.js',
        publicPath: '/' //部署地址
    }
}

这里publicPath指的是部署的服务器的地址,需要部署的资源都在dist目录下。也就是说,我们本地的dist目录下的文件最终被部署到的服务器的路径是什么,publicPath就需要配置成什么。

建立html文件

成功打包该js文件之后,接下来就是将js文件引入到html中,不然页面上哪看😂?接下来我们利用webpack的能力使每次打包之后,都可以将相关的js文件引入到html文件。
所用到的插件是html-webpack-plugin

  • 首先安装它:npm install html-webpack-plugin -D
  • 新建public/index.html文件,然后直接用快捷键html:5直接生成一个最基本的html内容
  • 在webpack.config.js中添加以下配置
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
		mode:"development",
    plugins: [
        new HtmlWebpackPlugin({
            template: './public/index.html',// 源html文件
            filename: 'index.html', //打包后的文件名
            hash: true //是否加上hash,默认是 false
        })
    ]
}
  • 执行npx webpack命令,dist目录下就生成了index.html文件并且引入了main.js。

除此之外该插件还提供了其他配置项,这个在某些时候可能很有用,详细的可以参考html-webpack-plugin配置项,我们放到之后在详细讲。

配置babel-loader

目前的配置只是将代码进行了打包,打包之后let 依然还是 let,没有进行转译,即将高级语法转译成低级语法,否则低版本浏览器就会出现兼容性问题。将高版本语法转译为低版本语法需要用到babel,接下来进行babel的配置。

  • 安装babel相关依赖
npm install @babel/core @babel/preset-env @babel/plugin-transform-runtime -D
npm install @babel/runtime @babel/runtime-corejs3
  • 安装babel-loader npm install babel-loader -D
  • 修改webpack.config.js配置
//webpack.config.js
module.exports = {
    //......
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                use: {
                  loader:'babel-loader',
                  options: {
                        presets: ["@babel/preset-env"],
                        plugins: [
                            [
                                "@babel/plugin-transform-runtime",
                                {
                                    "corejs": 3
                                }
                            ]
                        ]
                    }
                },
                exclude: /node_modules/ //排除 node_modules 目录,
            }
        ]
    }
  	// plugins:[...]
}

除了在webpack.config.js中进行配置,也可以在babel.config.js或者.babelrc文件中进行配置,webpack会自动到.babelrc中寻找相关配置,比如建立.babelrc文件并配置如下

{
    "presets": ["@babel/preset-env"],
    "plugins": [
        [
            "@babel/plugin-transform-runtime",
            {
                "corejs": 3
            }
        ]
    ]
}

再次执行npx webpack 此时你会发现dist/index.js中的let已经被转译成var了。
关于babel的更详细解释大家可以参考这个,babel最详细解读

热更新--配置devServer

webpack支持热更新,也就是实时查看页面效果,此功能需要配置devServer,需要用到的插件是webpack-dev-server,这个插件会在本地起一个服务,在这个服务下可以访问页面

  • 安装:npm i webpack-dev-server -D
  • 为了显示效果,给我们的html文件加上点内容
  1. 添加配置如下
//webpack.config.js
module.exports = {
    //...
    devServer: {
        port: '3000', //默认是8080
        quiet: false, //默认不启用
        inline: true, //默认开启 inline 模式,如果设置为false,开启 iframe 模式
        stats: "errors-only", //终端仅打印 error
        overlay: false, //默认不启用
        clientLogLevel: "silent", //日志等级
        compress: true //是否启用 gzip 压缩
    }
}

解释一波:

  1. 启用 quiet 后,除了初始启动信息之外的任何内容都不会被打印到控制台。这也意味着来自 webpack 的错误或警告在控制台不可见 ———— 我是不会开启这个的,看不到错误日志,还搞个锤子
  2. stats: "errors-only" , 终端中仅打印出 error,注意当启用了 quiet 或者是 noInfo 时,此属性不起作用。 ————— 这个属性个人觉得很有用,尤其是我们启用了 eslint 或者使用 TS进行开发的时候,太多的编译信息在终端中,会干扰到我们。
  3. 启用 overlay 后,当编译出错时,会在浏览器窗口全屏输出错误,默认是关闭的。
  4. clientLogLevel: 当使用内联模式时,在浏览器的控制台将显示消息,如:在重新加载之前,在一个错误之前,或者模块热替换启用时。如果你不喜欢看这些信息,可以将其设置为 silent (none 即将被移除)。
  • 执行命令 npx webpack-dev-server,打开localhost:3000就可以查看页面内容了,如果修改页面内容,页面也会自动刷新

配置sourceMap

我们打开控制台,发现我们输出的log被定位在第8行,点开之后也发现这已经是打包转译过的代码,那么这样的话就不利于我们开发时定位问题。

image.png

借助webpack的devtool来设置sourceMap可以帮助我们解决问题
添加配置如下:

//webpack.config.js
module.exports = {
    // ....
    devtool: 'cheap-module-eval-source-map' //开发模式下比较推荐的一个值
    // ....
}

这样的设置即可开启sourceMap,开发模式下就可以映射到我们的源代码了
执行命令,你会发现log信息可以被准确定位到哪一行了
cheap、module、eval的不同组合表示不同级别的sourceMap,详细的可以参考开发工具devtool

处理样式文件

我们已经知道,webpack只能js,对于css,我们需要配置响应的loader,对于样式文件,我们一般需要的是:
style-loader:将css插入到style标签中
css-loader:负责处理 @import 等语句。
postcss-loader:兼容性问题,根据浏览器自动添加前缀
其他:如果你要用 less 或者是 sass 的话,还需要 less-loadersass-loader
这里配置一下 css 文件,其他的配置也比较简单,大家可以自行查阅文档进行配置

  • 安装依赖:npm install style-loader css-loader postcss-loader autoprefixer -D
  • 修改配置如下
module.exports = {
    // ....
    moudle:{
      rules:[
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader',
          {
            loader: 'postcss-loader',
            options: {
              plugins: [
                require('autoprefixer')
              ]
            }
          }
        ]
      ]
    }
    // ....
}
  • 建立文件src/index.css,然后在index.js中引入此css文件import'./index.less'即可
body {
    color: red;
}
  • 重新执行命令,样式即可生效,说明样式的打包也ok了。

处理图片、文字等文件

图片、文字等资源同样不能直接被webpack打包,首先需要相应的loader进行处理。

  • 首先安装依赖:npm install url-loader -D
  • 修改webpack配置
//webpack.config.js
module.exports = {
    //...
    modules: {
        rules: [
            {
                test: /\.(png|jpg|gif|jpeg|webp|svg|eot|ttf|woff|woff2)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 10240, //低于10k的资源将会被转化成base64
                            esModule: false // 设置为 false,否则,<img src={require('XXX.jpg')} /> 会出现 <img src=[Module Object] />
                        }
                    },
                  	{
                        loader: 'file-loader'
                    }
                ],
                exclude: /node_modules/
            }
        ]
    }
}

limit限制为10240是比较推荐的,base64的好处是可以减少网络请求,但是大量的base64会导致数据量变大,设置为10k是属于比较平衡这两者的选择,而超过10k的将会被拷贝到dist目录下。当然我们也可以指定路径,通过配置file-loader中option的outpath选项即可,例如outputPath: 'images/'
更多配置可参考官方文档

多页面应用的配置

假设我们的项目是一个多页面应用,有login.html和index.html,那么如何配置呢?这里需要对入口、出口以及html-webpack-plugin做相应的配置。

  • 在public下再建立一个login.html文件
  • 然后添加以下配置
//webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// ....
module.exports = {
    entry: {
        index: './src/index.js',
        login: './src/login.js'
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[hash:6].js'
    },
    //...
    plugins: [
        new HtmlWebpackPlugin({
            template: './public/index.html',
            filename: 'index.html', //打包后的文件名
            chunks: ['index']   // 只引入index.js
        }),
        new HtmlWebpackPlugin({
            template: './public/login.html',
            filename: 'login.html', //打包后的文件名
            chunks: ['login']   // 只引入login.js
        }),
    ]
}
  • 执行打包命令 npx webpack ,执行结果如下

image.png

小结

本篇带大家认识了一下webpack,讲了一下webpack的基本配置。webpack还有其他的很多东西......但是核心概念依然是入口、出口、loader和plugin,在此基础上拓展出了很多优秀的能力(这也是webapck能火的主要原因)。学习webpack,需要认真理解这四个核心概念,然后多多实践,遇到问题、解决问题,知识慢慢就掌握了。

参考

感谢一下各位大佬的文档
juejin.cn/post/684490…
www.webpackjs.com/concepts/