简介
webpack是什么?
实质上,webpack 是一个古代 JavaScript 应用程序的动态模块打包器(static module bundler)。在 webpack 解决应用程序时,它会在外部创立一个依赖图(dependency graph),用于映射到我的项目须要的每个模块,而后将所有这些依赖生成到一个或多个bundle。—— 来自 webpack 官网文档
简略的说,webpack就是一个古代前端开发的打包机。古代前端十分风行应用模块化的形式开发,webpack所做的就是将各个js模块正当的打包成bundle或者chunk。在打包的过程中,能够通过loader的形式,将新的js语法、CSS预处理语言等转换成更容易被浏览器反对的模式。
webpack是基于nodejs的,在绝大部分时,在应用时须要为它写一个配置文件。这个配置文件的次要构造如下:
module.exports = { mode: 'development' // 模式配置 entry: '', // 入口文件 output: {}, // 进口文件 module: {}, // 解决对应模块 plugins: [], // 对应的插件 devServer: {}, // 开发服务器配置(仅在开发中应用)}
接下来,咱们就来一步步的实现这些配置。
筹备
初始化和装置
在指定文件夹下执行 npm init
进行初始化。
mkdir webpackDemo&&npm init
因为我的项目并不是一个要公布到npm的我的项目,所以执行npm init后只用一路回车即可。
装置webpack和react的依赖:
npm install --save-dev webpack react react-dom
在webpack4之后的版本中,还须要装置webpack-cli,具体方法同上。
创立初始目录构造和文件
在我的项目根目录创立config文件夹,并在内创立webpack.config.js。
关上根目录下的package.json 配置scripts
:
"scripts": { "build": "webpack --mode production --config ./config/webpack.config.js", }
配置scripts脚本是为了前期在执行过程中只用在命令行中输出 npm '脚本中指定配置' 就可能实现命令行的输出操作。比方输出 npm build,就会主动执行 "webpack --mode production --config ./config/webpack.config.js" 这一长串的操作。
创立代码文件夹和react的入口文件:
在我的项目根目录中创立src文件夹,并在内创立index.js、App.js、index.css。
index.js
import React from 'react';import ReactDOM from 'react-dom';import App from './App';import './index.css'ReactDOM.render( <App />, document.getElementById('root'));
App.js
import React from 'react';export default class App extends React.Component { render() { return <div> <p className="text">入手搭建一个基于webpack4的react开发环境</p> </div> }}
index.css
.text{ color:'red'}
实现上述操作后,我的项目目录构造应该像上面这样
webpackDemo│ node_modules└───config │ webpack.config.js└───src │ index.js │ index.css │ App.js package.json
当初,咱们实现了简略的初始化工作,上面开始理解webpack吧。
模式 (mode)
mode是webpack4中新增的概念,它有三个选项:development
、production
、none
,用来设置webpack的优化形式。
development
开发模式,该模式优化了开发速度,提供了具体的谬误机制和浏览器调试工具。并且敞开了代码压缩,使代码可能更快的构建。
production
生产模式,该模式可能提供更小的代码包,去除了只在开发阶段运行的代码。主动开启了代码混同压缩。
配置
module.export = { mode:'production' // 'development'||'production'||'none'}
程序入口 (entry)
在这里,能够申明一个利用的终点。入口能够有一个或者多个。在单页利用中,入口个别只有一个。不过也能够将公共依赖配置成为单页利用的入口,这样单页利用也能够有多个入口。而在多页利用中,个别会有多个入口文件。
一个简略的单页利用入口如下:
module.export = { mode:'production' // 'development'||'production'||'none', entry:'./src/index.js',}
输入 (output)
output用来配置我的项目打包后的文件名称、门路。用来通知webpack怎么输入、输入到哪、叫什么名字。
const path = require('path');module.export = { mode:'production' // 'development'||'production'||'none', entry:'./src/index.js', output: { // 在bundle中引入正文 留神:该选项不应该在生产模式中启用 pathinfo:true, // 所有输入文件的指标门路 // 必须是绝对路径(应用 Node.js 的 path 模块) path: path.resolve(__dirname, './../build'), // 输入的文件名配置 filename: "[name].[hash].js" }}
这里的filename并没有给它一个理论的名称,而是应用模板字符串去设置webpack生成后的文件名称。这个设置中的[name]代表模块名称,在单入口文件中默认为main。而[hash]则会生成一个模块标识符的hash,默认是20位,能够通过[hash:16]的形式指定它的位数。打包后的文件名称就像这样main.f236aaeca342dfb1f8dd.js
。在生成文件名称后跟上hash有助于咱们在我的项目重新部署后因为援用的文件名称变了,浏览器会去下载新的文件,不再持续应用本地的缓存。
loader
webpack的作用就是将前端开发中的各个模块进行解决以及打包。而loader的作用就是解决webpack中的这些模块。
webpack中模块有很多种,常见的有:
- 模块化的js文件
- css/less/sass文件
- 图片以及动态文件
loader在module中配置:
// 示例const path = require('path');const appSrc = path.resolve(__dirname, '../src')module.exports = { mode: 'development', // 入口 entry: './src/index.js', // 进口 output: { pathinfo: true, // 所有输入文件的指标门路 // 必须是绝对路径(应用 Node.js 的 path 模块) path: path.resolve(__dirname, './../build'), // 输入的文件名配置 filename: "bundle.js" }, module: { rules: [ { test: /\.(js|jsx)$/, // 用来指定针对的文件类型 反对正则 exclude: /node_modules/, // 用来指定须要排除的文件夹,优化打包速度 include: appSrc, // 指定所蕴含的文件夹 ,优化打包速度 loader: "babel-loader", // 针对指定文件应用的loader } ] }};
要对这些模块进行解决,就要应用到不同的loader。在此之前,先简略的介绍一下须要应用到的loader。
babel-loader
babel是一个语法转换器,可能让你自在的应用JavaScript的最新语法。它可能将咱们所写的新语法、jsx等转换成浏览器可能敌对反对的模式。
要应用babel-loader须要下列依赖,能够通过执行npm install --save-dev babel-loader @babel/core @babel/preset-react @babel/preset-env
装置它们。
- babel-loader
- @babel/core
babel的外围组件,外面蕴含着babel的api。 - @babel/preset-env
用来本义JavaScript语法。 - @babel/preset-react
用来本义react。
配置babel-loader:
const path = require('path');const appSrc = path.resolve(__dirname, '../src')module.exports = { mode: 'development', // 入口 entry: './src/index.js', // 进口 output: { pathinfo: true, // 所有输入文件的指标门路 // 必须是绝对路径(应用 Node.js 的 path 模块) path: path.resolve(__dirname, './../build'), // 输入的文件名配置 filename: "bundle.js" }, module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, include: appSrc, loader: "babel-loader", options: { // 指定babel预处理本义 presets: ["@babel/preset-env", "@babel/preset-react"] } } ] }}
实现上述配置后,还需配置一下babel,让它可能转换react和js的新语法。能够像下面应用webpack配置中的option选项中的presets字段指定babel预处理的形式。
也能够在我的项目的根目录创立babel的配置文件.babelrc
。.babelrc
后缀rc来自linux中,应用过linux就晓得linux中很多rc结尾的文件,比方.bashrc,rc是run command的缩写,翻译成中文就是运行时的命令,示意程序执行时就会来调用这个文件。
babel所有的操作根本都会来读取这个配置文件,除了一些在回调函数中设置options参数的,如果没有这个配置文件,会从package.json文件的babel属性中读取配置。
在.babelrc
中增加下列语句:
{ "presets": ["@babel/preset-env","@babel/preset-react"]}
url-loader
url-loader和file-loader的作用相似,都是使webpack可能打包动态文件。url-loader相较于file-loader的性能更弱小,它可能应用两种形式进行打包。
url-loader有一个重要的参数 limit
,这个参数用来设置打包文件大小的限度。当文件小于指定参数时,它可能返回一个DataURL(base64)局势的文件。当文件大于指定参数时,它将通过file-loader进行打包。
配置url-loader:
const path = require('path');const appSrc = path.resolve(__dirname, '../src')module.exports = { mode: 'development', // 入口 entry: './src/index.js', // 进口 output: { pathinfo: true, // 所有输入文件的指标门路 // 必须是绝对路径(应用 Node.js 的 path 模块) path: path.resolve(__dirname, './../build'), // 输入的文件名配置 filename: "bundle.js" }, module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, include: appSrc, loader: "babel-loader", options: { // 指定babel预处理本义 presets: ["@babel/preset-env", "@babel/preset-react"] } }, // url-loader的配置 { test: /\.(png|jpg|gif)$/, loader: "url-loader", options: { // 设置url-loader转DataURL的文件大小下限 limit: 10000 } } ] }}
url-loader还有两个参数mimetype
和fallback
,这两个参数应用的并不多,就不在这里赘述了。
style-loader和css-loader
style-loader和css-loader都是用来解决css文件的,不过它们的作用并不相同。
css-loader:用来读取css文件的内容,并进行解决 如:minimize。
style-loader:将通过import模式导入到js中的css文件插入到<style></style>
标签内。
在webpack中的配置如下:
const path = require('path');const appSrc = path.resolve(__dirname, '../src')module.exports = { mode: 'development', // 入口 entry: './src/index.js', // 进口 output: { pathinfo: true, // 所有输入文件的指标门路 // 必须是绝对路径(应用 Node.js 的 path 模块) path: path.resolve(__dirname, './../build'), // 输入的文件名配置 filename: "bundle.js" }, module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, include: appSrc, loader: "babel-loader", options: { // 指定babel预处理本义 presets: ["@babel/preset-env", "@babel/preset-react"] } }, { test: /\.(png|jpg|gif)$/, loader: "url-loader", options: { // 设置url-loader转DataURL的文件大小下限 limit: 10000 } }, // 针对css文件配置style-loader和css-loader { test: /\.css$/, include: appSrc, use: [ 'style-loader', { loader: 'css-loader', options: { // 能够蕴含一些配置 modules:true|false, // 是否开启css模块化,开启后引入的css文件仅针对以后页面无效,不会作用到全局 minimize: true // 开发模式下应该设为false,优化打包速度 } } ] } ] }}
如上所示,当咱们在针对同一类型的文件配置多个loader时。能够将loader申明在一个数组内,数组项能够是一个对象,也能够仅仅是一个字符串,这取决于你针对某个loader还有没有非凡的设置。比方在配置css-loader时,还申明了option选项,并在option选项内开启了minimize选项。然而在配置style-loader时,仅仅写了一个字符串。
须要留神的是,数组内loader的执行程序是从数组的最初一项顺次向前执行。所有咱们将css-loader配置在了前面,它是先执行的。这更合乎解决逻辑,先对css进行解决,再插入到html中。
插件
插件是webpack的一个极其重要的性能,webpack提供了丰盛的插件接口,使开发者可能自在的开发插件来拓展webpack的性能。
这里咱们拿赫赫有名的 HtmlWebpackPlugin
来举例。
构想一个场景,在打包时,须要手动的去创立一个html文件,而后在其中引入打包好的各种文件。即便创立好html文件后,因为在config中设置了hash模式的打包文件名称。咱们在每次打包后还须要依据hash名称的变动去扭转html内引入的文件名称,这是十分低级的反复劳作。
HtmlWebpackPlugin
为咱们解决了这个问题。HtmlWebpackPlugin
可能依据咱们提供的模板主动生成html文件,并引入打包后的内容。
上面介绍一下HtmlWebpackPlugin
的应用过程。
装置:npm install --save-dev html-webpack-plugin
装置实现后,先在我的项目的根目录创立一个文件夹public
,在其中创立一个模板文件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>Document</title></head><body> <div id="root"></div></body></html>
而后在webpack中配置插件:
const path = require('path');const appSrc = path.resolve(__dirname, '../src')// 引入html-webpack-plugin插件const HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = { mode: 'development', // 入口 entry: './src/index.js', // 进口 output: { pathinfo: true, // 所有输入文件的指标门路 // 必须是绝对路径(应用 Node.js 的 path 模块) path: path.resolve(__dirname, './../build'), // 输入的文件名配置 filename: "bundle.js" }, module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, include: appSrc, loader: "babel-loader", options: { // 指定babel预处理本义 presets: ["@babel/preset-env", "@babel/preset-react"] } }, { test: /\.(png|jpg|gif)$/, loader: "url-loader", options: { // 设置url-loader转DataURL的文件大小下限 limit: 10000 } }, // 针对css文件配置style-loader和css-loader { test: /\.css$/, include: appSrc, use: [ 'style-loader', { loader: 'css-loader', options: { // 能够蕴含一些配置 minimize: true // 开发模式下应该设为false,优化打包速度 } } ] } ] }, plugins: [ // HTML模板文件解决插件 new HtmlWebpackPlugin({ file: 'index.html', // 生成的文件名称 template: 'public/index.html' // 指定模板文件 }) ],}
当初在命令行中执行npm build
,webpack将打包src目录内的文件。并将在根目录生成一个build文件,将打包的内容输入在外面。
这时候,咱们其实曾经实现了webpack的根本配置。然而当初的配置是基于development模式进行打包的,没有进行压缩,很显然这并不能做为一个可公布的版本。要批改为生产模式其实也很简略,能够通过两种形式去实现。
- 批改配置文件中的mode选项,将development批改为production。
- 删除配置中的mode选项,批改package.json scripts中的build项为
webpack --mode production --config ./config/webpack.config.js
。
在配置2中,应用--mode 可能为webpack-cli设置打包模式。批改后再次打包,这时候代码通过webpack production模式的优化,进行了混同压缩,变成了公布版本。
devServer
在日常的开发过程中,必定不能每批改一点货色就从新build一次,这样开发效率会受到很大的影响。这时须要启动一个服务,来监听文件的变动。当文件保留时就从新打包,同时帮咱们主动刷新浏览器,不便咱们及时察看到更新。
要实现上述操作有几种形式,这里只介绍其中的一种,应用 webpack-dev-server
插件。
执行 npm install --save-dev webpack-dev-server
装置插件,在module.explot中增加配置项 devServer
。
devServer的配置项有很多,这里大略的介绍其中几种罕用的配置:
- contentBase: '',通知服务器从哪个目录中提供内容
- https: true|false, 是否启用https
- compress: true|false, 是否启用压缩
- host: '127.0.0.1', 指定host地址
- port: 23333, 指定端口
- overlay: true|false, 当呈现编译器谬误或正告时,在浏览器中显示全屏覆盖层。
- progress: true|false, 将运行进度输入到控制台。
将devServer增加到配置中:
const path = require('path');const appSrc = path.resolve(__dirname, '../src')// 引入html-webpack-plugin插件const HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = { // 入口 entry: './src/index.js', // 进口 output: { pathinfo: true, // 所有输入文件的指标门路 // 必须是绝对路径(应用 Node.js 的 path 模块) path: path.resolve(__dirname, './../build'), // 输入的文件名配置 filename: "bundle.js" }, module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, include: appSrc, loader: "babel-loader", options: { // 指定babel预处理本义 presets: ["@babel/preset-env", "@babel/preset-react"] } }, { test: /\.(png|jpg|gif)$/, loader: "url-loader", options: { // 设置url-loader转DataURL的文件大小下限 limit: 10000 } }, // 针对css文件配置style-loader和css-loader { test: /\.css$/, include: appSrc, use: [ 'style-loader', { loader: 'css-loader', options: { // 能够蕴含一些配置 minimize: true // 开发模式下应该设为false,优化打包速度 } } ] } ] }, devServer: { // HOST host: '127.0.0.1', // 端口 port: 23333, // 报错提醒在网页遮罩层 overlay: true, // 显示运行进度 progress: true, }, plugins: [ // HTML模板文件解决插件 new HtmlWebpackPlugin({ file: 'index.html', // 生成的文件名称 template: 'public/index.html' // 指定模板文件 }) ]}
须要留神的时,devServer该当用在开发环境中,所以当初须要将之前的配置进行批改。
- 在配置中删除mode项。
- 为package.json的scripts中增加另一个启动命令
"start": "webpack-dev-server --open --mode development --config ./config/webpack.config.js"
- 将之前的build项改为
webpack --mode production --config ./config/webpack.config.js
。
当初,执行npm build,webpack将应用production模式进行打包。执行npm start时,将应用development模式进行打包,并且webpack-dev-server将启动一个服务,监听文件变更。
当初执行npm start,就能够开始进行开发了!
进阶
在下面的配置中,咱们曾经实现了一个react我的项目开发环境的根本配置。但这远远不够,在理论的我的项目中,可能会用到很多的工具来优化开发速度。同时也须要针对不同的环境写不同的配置,做不同的优化等。并且,可能还波及到代码宰割、压缩等配置。
上面,咱们来一步步欠缺webpack的配置。
devtool
webpack中devtool选项用来管制是否生成,以及如何生成 source map。
想要理解source map,能够看一下这篇文章。简略的说,source map就是帮忙咱们定位到错误信息地位的文件。正确的配置source map,可能进步开发效率,更快的定位到谬误地位。
webpack中devtool有很多种配置,咱们能够在 这里 理解它。
在开发环境中,更举荐应用cheap-module-eval-source-map
,它能帮忙咱们精确的定位到谬误源代码地位的同时,也能提供更快的构建速度和构建性能。
而在生产环境中,能够不启动任何source map(不配置devtool项),也能够应用source-map
。须要留神的是,不要将source map部署到生产服务器中。
为svg文件配置loader
个别状况下,我的项目都会须要用到图标。常见的图标应用形式有很多种,如雪碧图、字体图标、svg等。雪碧图和iconfont的应用形式不须要进行非凡的解决,这里咱们就不再赘述。上面介绍一个应用svg图标的办法。
通过 svgr ,可能间接将svg图标以react组件的模式引入我的项目中。
就像这样:
import React from 'react';import { ReactComponent as Icon } from './icon.svg';export default class App extends React.Component { render() { return <div> <Icon width={10} height={10} /> </div> }}
在react最新版本的cli create-react-app
,已近默认集成了svgr。在咱们本人的我的项目中应用也很简略,只须要针对 .svg
增加loader即可。
{ test: /\.svg$/, use: ['@svgr/webpack'],}
svgr同时也反对node、react-native等解决形式,能够通过 svgr文档来理解。
构建不同环境下的配置
在生产环境和开发环境的构建指标差别很大。比方在开发环境中,须要更快的构建速度和更强的谬误提醒。然而在生产环境中,则心愿构建的代码能更小,更轻,更侧重于性能。所以,针对不同的环境,须要不同的配置文件。然而如果将配置齐全拆离开,两个配置文件中可能会蕴含很多反复的代码。这时咱们须要提出公共的配置,为了将这些配置合并在一起,能够应用webpack-merge。
上面,咱们开始应用 webpack-merge 进行配置优化。
首先,应用npm装置依赖 npm install --save-dev webpack-merge
而后,在config文件夹下创立 webpack.config.common.js 、 webpack.config.dev.js 、webpack.config.prod.js。顾名思义,这三个配置代表了通用、开发、生产模式的配置文件。
将之前配置中用到的公共配置提出到 webpack.config.common.js 内:
// webpack.config.common.js// 打包HTML文件const HtmlWebpackPlugin = require('html-webpack-plugin');const path = require('path');const appSrc = path.resolve(__dirname, '../src')module.exports = { // 入口 entry: './src/index.js', module: { rules: [ { // 配置svg图标loader,能够在我的项目中通过组件的模式间接引入svg图标 test: /\.svg$/, include: appSrc, use: ['@svgr/webpack'] } ] }, plugins: [ // HTML模板文件解决插件 new HtmlWebpackPlugin({ file: 'index.html', template: 'public/index.html' }) ]}
开发环境下的配置:
const merge = require('webpack-merge');// 引入公共配置文件const common = require('./webpack.config.common.js');const path = require('path');const appSrc = path.resolve(__dirname, '../src')module.exports = merge(common, { mode: 'development', devtool: 'cheap-module-eval-source-map', // 进口 output: { pathinfo: true, // 所有输入文件的指标门路 // 必须是绝对路径(应用 Node.js 的 path 模块) // chunk名称配置 chunkFilename: '[name].chunk.js', // 输入的文件名配置 filename: "bundle.js" }, module: { rules: [ { test: /\.(js|jsx)$/, // exclude: /node_modules/, include: appSrc, loader: "babel-loader", options: { presets: ["@babel/preset-env", "@babel/preset-react"] } }, // 针对动态文件 { test: /\.(png|jpg|gif)$/, loader: "url-loader", options: { limit: 8192, name: 'static/[name].[hash:8].[ext]', } }, { test: /\.css$/, use: [ MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { minimize: false } } ] } ] }, devServer: { // HOST host: '127.0.0.1', // 端口 port: 23333, // 报错提醒在网页遮罩层 overlay: true, // 显示运行进度 progress: true, }})
生产环境配置文件:
const path = require('path');const merge = require('webpack-merge');const common = require('./webpack.config.common.js');// 每次执行打包 先革除之前的打包文件const CleanWebpackPlugin = require('clean-webpack-plugin');const appSrc = path.resolve(__dirname,'../src')module.exports = merge(common, { mode: 'production', // 进口 output: { pathinfo: false, chunkFilename: 'js/[name].chunk.js', // 所有输入文件的指标门路 // 必须是绝对路径(应用 Node.js 的 path 模块) path: path.resolve(__dirname, './../build'), filename: "js/[name].[chunkhash:8].js" }, module: { rules: [ { test: /\.(js|jsx)$/, include: appSrc, // exclude: /node_modules/, loader: "babel-loader", options: { presets: ["@babel/preset-env", "@babel/preset-react"] } }, // 针对动态文件 { test: /\.(png|jpg|gif)$/, loader: "url-loader", options: { limit: 10000, name: 'static/[name].[hash:8].[ext]', } }, { test: /\.css$/, use: [ MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { minimize: true } } ] } ] }, plugins: [ // 打包前革除之前的build目录 new CleanWebpackPlugin(['build'], path.resolve(__dirname, '../')) ]});
当初配置曾经批改实现,咱们还须要批改一下package.json,让启动命令去援用不同的配置文件。
将开发模式的启动配置批改为 "start": "webpack-dev-server --open --mode development --config ./config/webpack.config.dev.js"
。
生产模式的启动配置批改为 "build": "webpack --mode production --config ./config/webpack.config.prod.js",
当初咱们应用npm start
命令启动我的项目,运行的是webpack.config.dev.js文件,这是开发配置文件,咱们能够在外面做一些针对开发模式的优化。
应用npm build
命令启动我的项目,运行的是webpack.config.prod.js文件,这是生产配置文件,咱们能够在外面做一些针对生产模式的优化。
避免打包文件的反复
执行build命令打包文件时,会在我的项目的根目录下生成build目录,并在其中生成打包文件。当执行屡次build后,会发现因为项目名称的hash值不同,build目录下可能存在多个版本打包后的文件。要解决这个问题,能够应用插件 clean-webpack-plugin
。
首先装置插件 npm i clean-webpack-plugin --save-dev
配置如下:
const CleanWebpackPlugin = require('clean-webpack-plugin')// webpack config{ plugins: [ new CleanWebpackPlugin(['build'], path.resolve(__dirname, '../')) ]}
配置完插件后,再执行npm build命令。会发现每次打包前,build目录都会被删除,而后从新创立。
留神,该插件只用于生产环境配置。
总结
到这里,咱们实现了webpack的根底的配置,以及各种概念的扫盲。其实这只能算是根底用法,要实现一个真正欠缺的webpack配置必定远远不止这些。
在把握了上述根底配置后,大家能够尝试着进行一些更深刻的学习,如optimization、tree shaking、生产环境下的构建速度优化等。