针对webpack,是你们(前端开发)在平常的开发中都会碰见的,经过书写的方式输出,学习到的关于前端工程化的小知识点的总结和学习,造成本身的知识体系css
webpack官网定义:html
webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序须要的每一个模块,而后将全部这些模块打包成一个或多个 bundle。
在开始了解webpack配置前,首先须要理解四个核心概念:前端
// npm 安装 npm install webpack webpack-cli -g // yarn 安装 yarn global add webpack webpack-cli
安装好后,能够在不适用配置文件的方法,直接对文件进行打包:webpack <entry> [<entry>] -o <output>
vue
新建一个项目,就一个入口文件,测试webpack打包:node
运行打包命令:webpack index.js
jquery
在这里咱们会看见一个WARNING
的信息,这是由于没有设置mode
,咱们只须要加一个参数-p
便可:webpack
webpack -p index
这样默认会生成一个dist
文件夹,里面有个main.js
文件:web
有了入口文件咱们还须要经过命令行定义一下输入路径dist/bundle.js
:面试
webpack -p index.js -o dist/bundle.js
命令行的打包构建方式仅限于简单的项目,若是在生产中,项目复杂,多个入口,咱们就不可能每次打包都输入一连串的入口文件地址,也难以记住;所以通常项目中都使用配置文件来进行打包;配置文件的命令方式以下:express
webpack [--config webpack.config.js]
配置文件默认的名称就是webpack.config.js
,一个项目中常常会有多套配置文件,咱们能够针对不一样环境配置不一样的额文件,经过--config
来进行更换:
// 开发环境 webpack --config webpack.config.dev.js // 生产环境 webpack --config webpack.config.prod.js
config
配置文件经过module.exports
导出一个配置对象:
// webpack.config.js const path = require('path') const resolve = function (dir) { return path.resolve(__dirname, dir) } module.exports = { entry: { app: resolve('../index.js') }, output: { filename: '[name].[hash:8].js', path: resolve('../dist') }, }
除了导出为对象,还能够导出为一个函数,函数中会带入命令行中传入的环境变量等参数,这样能够更方便的对环境变量进行配置;好比咱们能够经过ENV
来区分不一样环境:
const path = require('path') const resolve = function (dir) { return path.resolve(__dirname, dir) } module.exports = function(ENV, argv) { return { // 其余配置 entry: resolve('../index.js'), output: {} } }
还能够导出为一个Promise,用于异步加载配置,好比能够动态加载入口文件:
entry: () => './demo' 或 entry: () => new Promise((resolve) => resolve(['./demo', './demo2']))
正如在上面提到的,入口是整个依赖关系的起点入口;咱们经常使用的单入口配置是一个页面的入口:
module.exports = { entry: resolve('../index.js') }
可是咱们项目中可能不止一个模块,所以须要将多个依赖文件一块儿注入,这时就须要用到数组了:
module.exports = { entry: [ '@babel/polyfill', resolve('../index.js') ] }
若是咱们项目中有多个入口起点,则就须要用到对象形式了:
// webpack 就会构建两个不一样的依赖关系 module.exports = { entry: { app: resolve('../index.js'), share: resolve('../share.js') } }
output
选项用来控制webpack如何输入编译后的文件模块;虽然能够有多个entry
,可是只能配置一个output
:
module.exports = { entry: resolve('../index.js'), output: { filename: 'index.js', path: resolve('../dist') }, }
这里咱们配置了一个单入口,输出也就是index.js
;可是若是存在多入口的模式就行不通了,webpack会提示Conflict: Multiple chunks emit assets to the same filename
,即多个文件资源有相同的文件名称;webpack提供了占位符
来确保每个输出的文件都有惟一的名称:
module.exports = { entry: { app: resolve('../index.js'), share: resolve('../index.js'), }, output: { filename: '[name].bundle.js', path: resolve('../dist') }, }
这样webpack打包出来的文件就会按照入口文件的名称来进行分别打包生成三个不一样的bundle文件;还有如下不一样的占位符字符串:
占位符 | 描述 |
---|---|
[hash] | 模块标识符(module identifier)的 hash |
[chunkhash] | chunk 内容的 hash |
[name] | 模块名称 |
[id] | 模块标识符 |
[query] | 模块的 query,例如,文件名 ? 后面的字符串 |
在这里引入module
、chunk
和bundle
的概念,上面代码中也常常会看到有这两个名词的出现,那么他们三者到底有什么区别呢?首先咱们发现module
是常常出如今咱们的代码中,好比module.exports
;而chunk
常常和entry
一块儿出现,bundle
老是和output
一块儿出现。
咱们经过下面这张图看能够加深对这三个概念的理解:
理解了chunk的概念,相信上面表中chunkhash和hash的区别也很容易理解了;
在webpack2和webpack3中咱们须要手动加入插件来进行代码的压缩、环境变量的定义,还须要注意环境的判断,十分的繁琐;在webpack4中直接提供了模式这一配置,开箱便可用;若是忽略配置,webpack还会发出警告。
module.exports = { mode: 'development/production' }
开发模式是告诉webpack,我如今是开发状态,也就是打包出来的内容要对开发友好,便于代码调试以及实现浏览器实时更新。
生产模式不用对开发友好,只须要关注打包的性能和生成更小体积的bundle。看到这里用到了不少Plugin,不用慌,下面咱们会一一解释他们的做用。
相信不少童鞋都曾有过疑问,为何这边DefinePlugin
定义环境变量的时候要用JSON.stringify("production")
,直接用"production"
不是更简单吗?
咱们首先来看下JSON.stringify("production")
生成了什么;运行结果是""production"
",注意这里,并非你眼睛花了或者屏幕上有小黑点,结果确实比"production"
多嵌套了一层引号。
咱们能够简单的把DefinePlugin
这个插件理解为将代码里的全部process.env.NODE_ENV
替换为字符串中的内容。假如咱们在代码中有以下判断环境的代码:
// webpack.config.js module.exports = { plugins: [ new webpack.DefinePlugin({ "process.env.NODE_ENV": "production" }), ] } // index.js if (process.env.NODE_ENV === 'production') { console.log('production'); }
这样生成出来的代码就会编译成这样:
//dist/bundle.js //代码中并无定义production变量 if (production === 'production') { console.log('production'); }
可是咱们代码中可能并无定义production
变量,所以会致使代码直接报错,因此咱们须要经过JSON.stringify
来包裹一层:
//webpack.config.js module.exports = { plugins: [ new webpack.DefinePlugin({ //"process.env.NODE_ENV": JSON.stringify("production") //至关于 "process.env.NODE_ENV": '"production"' }), ] } //dist/bundle.js if ("production" === 'production') { console.log('production'); }
在上面的代码中咱们发现都是手动来生成index.html,而后引入打包后的bundle文件,可是这样太过繁琐,并且若是生成的bundle文件引入了hash值,每次生成的文件名称不同,所以咱们须要一个自动生成html的插件;首先咱们须要安装这个插件:yarn add html-webpack-plugin -D 或者 npm install html-webpack-plugin -D
使用:
const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { // 其余代码 plugins: [ new HtmlWebpackPlugin({ // 模板文件 template: resolve('../public/index.html'), // 生成的html名称 filename: 'index.html', // icon favicon: resolve('../public/logo.ico') }), ] }
loader 用于对模块的源代码进行转换。默认webpack只能识别commonjs代码,可是咱们在代码中会引入好比vue、ts、less等文件,webpack就处理不过来了;loader拓展了webpack处理多种文件类型的能力,将这些文件转换成浏览器可以渲染的js、css。
module.rules
容许咱们配置多个loader,可以很清晰的看出当前文件类型应用了哪些loader。
module.exports = { module: { rules: [ { test: /\.css$/, use: 'css-loader' }, { test: /\.ts$/, use: 'ts-loader' } ] } };
loader 特性
loader 经过(loader)预处理函数,为 JavaScript 生态系统提供了更多能力。 用户如今能够更加灵活地引入细粒度逻辑,例如压缩、打包、语言翻译和其余更多。
兼容低版本浏览器的痛相信不少童鞋都经历过,写完代码发现本身的js代码不能运行在IE10或者IE11上,而后尝试着引入各类polyfill;babel的出现给咱们提供了便利,将高版本的ES6甚至ES7转为ES5;咱们首先安装babel所须要的依赖:yarn add -D babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime
因为babel-loader的转译速度很慢,在后面咱们加入了时间插件后能够看到每一个loader的耗时,babel-loader是最耗时间;所以咱们要尽量少的使用babel来转译文件,正则上使用$来进行精确匹配,经过exclude将node_modules
中的文件进行排除,include
将只匹配src
中的文件;能够看出来include的范围比exclude更缩小更精确,所以也是推荐使用include。
// 省略其余代码 module: { rules: [ { test: /\.js$/, exclude: /node_modules/, include: [resolve('src')] use: { loader: 'babel-loader', options: { presets: [ ['@babel/preset-env', { targets: "defaults" }] ], plugins: ['@babel/plugin-proposal-class-properties'] } } } ] }
file-loader
和url-loader
都是用来处理图片、字体图标等文件;url-loader
工做时分两种状况:当文件大小小于limit参数,url-loader
将文件转为base-64编码,用于减小http请求;当文件大小大于limit参数时,调用file-loader
进行处理;所以咱们优先使用url-loader
。
module: { rules: [ { test: /\.(jpe?g|png|gif)$/i, //图片文件 use: [ { loader: 'url-loader', options: { // 10K limit: 1024, //资源路径 outputPath: resolve('../dist/images') }, } ], exclude: /node_modules/ }, ] }
在上面咱们都是经过命令行打包生成 dist 文件,而后直接打开html或者经过static-server来查看页面的;可是开发中咱们写完代码每次都来打包会严重影响开发的效率,咱们指望的是写完代码后当即就可以看到页面的效果;webpack-dev-server
就很好的提供了一个简单的web服务器,可以实时从新加载。
webpack-dev-server
的用法和wepack
同样,只不过他会额外启动一个express
的服务器。咱们在项目中webpack.config.dev.js
配置文件对开发环境进行一个配置:
module.exports = { mode: 'development', plugins: [ new Webpack.HotModuleReplacementPlugin() ], devtool: 'cheap-module-eval-source-map', devServer: { // 端口 port: 3300, // 启用模块热替换 hot: true, // 自动打开浏览器 open: true, // 设置代理 proxy:{ "/api/**":{ "target":"http://127.0.0.1:8075/", "changeOrigin": true } } } }
经过命令行webpack-dev-server
来启动服务器,启动后咱们发现根目录并无生成任何文件,由于webpack
打包到了内存中,不生成文件的缘由在于访问内存中的代码比访问文件中的代码更快。
咱们在public/index.html
的页面上有时候会引用一些本地的静态文件,直接打开页面的会发现这些静态文件的引用失效了,咱们能够修改server
的工做目录,同时指定多个静态资源的目录:
contentBase: [ path.join(__dirname, "public"), path.join(__dirname, "assets") ]
热更新(Hot Module Replacemen简称HMR)是在对代码进行修改并保存以后,webpack对代码从新打包,而且将新的模块发送到浏览器端,浏览器经过新的模块替换老的模块,这样就能在不刷新浏览器的前提下实现页面的更新。
上面介绍了DefinePlugin、HtmlWebpackPlugin等不少插件,咱们发现这些插件都可以不一样程度的影响着webpack的构建过程,下面还有一些经常使用的插件:
clean-webpack-pluginclean-webpack-plugin
用于在打包前清理上一次项目生成的bundle文件,它会根据output.path自动清理文件夹;这个插件在生产环境用的频率很是高,由于生产环境常常会经过hash生成不少bundle文件,若是不进行清理的话每次都会生成新的,致使文件夹很是庞大。
const { CleanWebpackPlugin } = require('clean-webpack-plugin') module.exports = { plugins: [ new CleanWebpackPlugin(), ], }
mini-css-extract-plugin
咱们在使用webpack构建工具的时候,经过style-loader
,能够把解析出来的css经过js插入内部样式表的方式到页面中,mini-css-extract-plugin插件也是用来提取css到单独的文件的,该插件有个前提条件,只能用于webpack 4及以上的版本,因此若是使用的webpack版本低于4,,那仍是用回extract-text-webpack-plugin插件。
const MiniCssExtractPlugin = require("mini-css-extract-plugin") module.exports = { // 省略其余代码 module: { rules: [ { test: /\.less$/, use: [ { loader: dev ? 'style-loader': MiniCssExtractPlugin.loader }, { loader: 'css-loader' }, { loader: 'less-loader' } ] } ] }, plugins: [ new MiniCssExtractPlugin({ filename: "[name].[hash:8].css", }) ] }
copy-webpack-plugin
咱们在public/index.html中引入了静态资源,可是打包的时候webpack并不会帮咱们拷贝到dist目录,所以copy-webpack-plugin就能够很好地帮我作拷贝的工做了。
const CopyWebpackPlugin = require('copy-webpack-plugin') module.exports = { plugins: [ new CleanWebpackPlugin(), new CopyWebpackPlugin([{ from: path.resolve(__dirname, '../static'), to: path.resolve(__dirname, '../dist/static') }]) ], }
ProvidePlugin
ProvidePlugin
能够很快的帮咱们加载想要引入的模块,而不用require
。通常咱们加载jQuery
须要先把它import
:
import $ from 'jquery' $('#layout').html('test')
可是咱们在config中配置ProvidePlugin
插件后可以不用import,直接使用$
:
module.exports = { plugins: [ new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' }), ] }
在项目中引入了太多模块而且没有require
会让人摸不着头脑,所以建议加载一些常见的好比jQuery、vue、lodash等。