Webpack是一个如今Javascript应用程序的模块化打包器,在Webpack中JS/CSS/图片等资源都被视为JS模块,简化了编程。当Webpack构建时,会递归造成一个模块依赖关系图,而后将全部的模块打包为一个或多个bundle。
本文内容javascript
要系统地学习Webpack,须要先了解Webpack的四个核心概念:css
webpack使用Node.js运行,所以全部的Node.js模块均可以使用,好比文件系统、路径等模块。html
对Node.js基础不太了解的读者,能够参考个人Node.js系列java
配置文件webpack.config.js
的通常格式为:node
const path = require('path'); // 导入Node.js的path模块 module.exports = { mode: 'development', // 工做模式 entry: './src/index', // 入口点 output: { // 输出配置 path: path.resolve(__dirname, 'dist'), // 输出文件的目录 filename: 'scripts/[name].[hash:8].js', // 输出JS模块的配置 chunkFilename:'scripts/[name].[chunkhash:8].js', // 公共JS配置 publicPath:'/' // 资源路径前缀,通常会使用CDN地址,这样图片和CSS就会使用CDN的绝对URL }, module:{ rules: [ { test:/\.(png|gif|jpg)$/, // 图片文件 use:[ { loader:'file-loader', // 使用file-loader加载 options:{ // file-loader使用的加载选项 name:'images/[name].[hash:8].[ext]' // 图片文件打包后的输出路径配置 } } ] } ] }, plugins:[ // 插件配置 new CleanWebpackPlugin() ] };
Webpack本身只管JS模块的输出,也就是output.filename是JS的配置,CSS、图片这些是经过loader来处理输出的
入口指明了Webpack从哪一个模块开始进行构建,Webpack会分析入口模块依赖到的模块(直接或间接),最终输出到一个被称为bundle的文件中。webpack
使用 entry来配置项目入口。
单一入口git
最终只会生成1个js文件github
module.exports = { entry: './src/index', };
多个入口web
最终会根据入口数量生成对应的js文件npm
module.exports = { entry:{ home:'./src/home/index', // 首页JS about:'./src/about/index' // 关于页JS } };
多个入口通常会在多页面应用中使用,好比传统的新闻网站。
输出指明了Webpack将bundle输出到哪一个目录,以及这些bundle如何命名等,默认的目录为./dist
。
module.exports = { output:{ path:path.resolve(__dirname, 'dist'), // 输出路径 filename:'scripts/[name].[hash:8].js', // 输出JS模块的文件名规范 chunkFilename:'scripts/[name].[chunkhash:8].js', // 公共JS的配置 publicPath:'/', // 资源路径前缀,通常会使用CDN地址,这样图片和CSS就会使用CDN的绝对URL } };
path
path是打包后bundle的输出目录,必须使用绝对路径。全部类型的模块(js/css/图片等)都会输出到该目录中,固然,咱们能够经过配置输出模块的名称规则来输出到path下的子目录。好比上例中最终输出的JS目录以下:
|----dist |---- scripts |---- home.aaaaaaaa.js
filename
入口模块输出的命名规则,在Webpack中,只有js是亲儿子,能够直接被Webpack处理,其余类型的文件(css/images等)须要经过loader来进行转换。
filename的经常使用的命名以下:
[name].[hash].js
chunkFilename
非入口模块输出的命名规则,通常是代码中引入其余依赖,同时使用了optimization.splitChunks配置会抽取该类型的chunk
hash
Webpack中常见的hash有hash
,contenthash
,chunkhash
,很容易弄混淆,这里说明一下。
publicPath
资源的路径前缀,打包以后的资源默认状况下都是相对路径,当更改了部署路径或者须要使用CDN地址时,该选项比较经常使用。
好比咱们把本地编译过程当中产生的全部资源都放到一个CDN路径中,能够这么定义:
publicPath: 'https://static.ddhigh.com/blog/'
那么最终编译的js,css,image等路径都是绝对连接。
loader用来在import时预处理文件,通常用来将非JS模块转换为JS能支持的模块,好比咱们直接import一个css文件会提示错误,此时就须要loader作转换了。
好比咱们使用loader来加载css文件。
module.exports = { module:{ rules:[ { test: /\.(css)$/, use: ['css-loader'] } ] } };
Webpack中有3种使用loader的方式:
module.rules用来配置loader。test用来对加载的文件名(包括目录)进行正则匹配,只有当匹配时才会应用对应loader。
多个loader配置时从右向左进行应用
配置式Webpack的loader也有好几种形式,有些是为了兼容而添加的,主要使用的方式有如下3种。
module.exports = { module:{ rules:[ { test: /\.less$/, loader:'css-loader!less-loader', // 多个loader中用感叹号分隔 }, { test:/\.css/, use:['css-loader'],//数组形式 }, { test:/\.(png|gif|jpg)$/, use:[ // loader传递参数时建议该方法 { loader: 'file-loader', options:{ // file-loader本身的参数,跟webpack无关 name: 'images/[name].[hash:8].js' } } ] } ] } };
每一个loader的options参数不必定相同,这个须要查看对应loader的官方文档。
loader通常用来作模块转换,而插件能够执行更多的任务,包括打包优化、压缩、文件拷贝等等。插件的功能很是强大,能够进行各类各样的任务。
下面是打包以前清空dist目录的插件配置示例。
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); module.exports = { plugins: [ new CleanWebpackPlugin(), ] };
插件也能够传入选项,通常在实例化时进行传入。
new MiniCssPlugin({ filename: 'styles/[name].[contenthash:8].css', chunkFilename: 'styles/[name].[contenthash:8].css' })
Webpack4中提取公共代码只须要配置optimization.splitChunks便可。
optimization: { splitChunks: { cacheGroups: { vendor: { // 名为vendor的chunk name: "vendor", test: /[\\/]node_modules[\\/]/, chunks: 'all', priority: 10 }, styles: { // 名为styles的chunk name: 'styles', test: /\.css$/, chunks: 'all' } } } },
上面的例子中将node_modules中的js打包为vendor,以css结尾的打包为styles
加载css文件
{ test:/\.css$/ loader:['css-loader'] }
加载less文件,通常须要配合css-loader
{ test:/\.less$/, loader:['css-loader','less-loader'] }
将文件拷贝到输出文件夹,并返回相对路径。通常经常使用在加载图片
{ test:/\.(png|gif|jpg)/, use:[ { loader:'file-loader', options:{ name:'images/[name].[hash:8].[ext]' } } ] }
转换ES2015+代码到ES5
{ test:/\.js$/, exclude: /(node_modules|bower_components)/, // 排除指定的模块 use:[ { loader:'babel-loader', options:{ presets:['@babel/preset-env'] } } ] }
转换Typescript到Javascript
{ test:/\.ts/, loader:'ts-loader' }
简化HTML的建立,该插件会自动将当前打包的资源(如JS、CSS)自动引用到HTML文件
const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { plugins:[ new HtmlWebpackPlugin() ] };
打包以前清理dist目录
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); module.exports = { plugins:[ new CleanWebpackPlugin() ] };
提取、压缩CSS,须要同时配置loader和plugin
const MiniCssPlugin = require('mini-css-extract-plugin'); module.exports = { module:{ rules:[ { test: /\.less$/, use: [MiniCssPlugin.loader, 'css-loader', 'less-loader'] }, { test: /\.css$/, use: [MiniCssPlugin.loader, 'css-loader'] }, ] }, plugins:[ new MiniCssPlugin({ filename: 'styles/[name].[contenthash:8].css', chunkFilename: 'styles/[name].[contenthash:8].css' }), ] };
下面使用Webpack来配置一个传统多页面网站开发的示例。
├── package.json ├── src │ ├── about 关于页 │ │ ├── index.html │ │ ├── index.js │ │ └── style.less │ ├── common │ │ └── style.less │ └── home 首页 │ ├── images │ │ └── logo.png │ ├── index.html │ ├── index.js │ └── style.less ├── webpack.config.js
"clean-webpack-plugin": "^3.0.0", "css-loader": "^3.2.1", "exports-loader": "^0.7.0", "extract-text-webpack-plugin": "^4.0.0-beta.0", "file-loader": "^5.0.2", "html-webpack-plugin": "^3.2.0", "html-withimg-loader": "^0.1.16", "less": "^3.10.3", "less-loader": "^5.0.0", "mini-css-extract-plugin": "^0.8.0", "normalize.css": "^8.0.1", "script-loader": "^0.7.2", "style-loader": "^1.0.1", "url-loader": "^3.0.0", "webpack": "^4.41.2", "webpack-cli": "^3.3.10", "webpack-dev-server": "^3.9.0", "zepto": "^1.2.0"
因为是传统多页网站,每一个页面都须要单独打包一份JS,所以每一个页面须要一个入口。
entry: { // 入口配置,每一个页面一个入口JS home: './src/home/index', // 首页 about: './src/about/index' // 关于页 }
本例咱们不进行CDN部署,所以输出点配置比较简单。
output: { // 输出配置 path: path.resolve(__dirname, 'dist'), // 输出资源目录 filename: 'scripts/[name].[hash:8].js', // 入口点JS命名规则 chunkFilename: 'scripts/[name]:[chunkhash:8].js', // 公共模块命名规则 publicPath: '/' // 资源路径前缀 }
本地开发时不须要每次都编译完Webpack再访问,经过webpack-dev-server,咱们能够边开发变查看效果,文件会实时编译。
devServer: { contentBase: './dist', // 开发服务器配置 hot: true // 热加载 },
本例中没有使用ES6进行编程,可是引用了一个非CommonJS的js模块Zepto
,传统用法中在HTML页面引入Zepto就会在window下挂载全局对象Zepto。可是在Webpack开发中不建议使用全局变量,不然模块化的优点将受到影响。
经过使用exports-loader和script-loader,咱们能够将Zepto包装为CommonJS模块进入导入。
module: { rules: [ { test: require.resolve('zepto'), loader: 'exports-loader?window.Zepto!script-loader' // 将window.Zepto包装为CommonJS模块 }, { test: /\.less$/, use: [MiniCssPlugin.loader, 'css-loader', 'less-loader'] }, { test: /\.css$/, use: [MiniCssPlugin.loader, 'css-loader'] }, { test: /\.(png|jpg|gif)$/, use: [ { loader: 'file-loader', options: { name: 'images/[name].[hash:8].[ext]' } } ] }, { test: /\.(htm|html)$/i, loader: 'html-withimg-loader' } ] },
主要进行公共模块的打包配置。
optimization: { splitChunks: { cacheGroups: { vendor: { name: "vendor", test: /[\\/]node_modules[\\/]/, chunks: 'all', priority: 10, // 优先级 }, styles: { name: 'styles', test: /\.css$/, chunks: 'all' } } } },
plugins: [ new CleanWebpackPlugin(), // 清理发布目录 new HtmlWebpackPlugin({ chunks: ['home', 'vendor', 'styles'], // 声明本页面使用到的模块,有主页,公共JS以及公共CSS filename: 'index.html', // 输出路径,这里直接输出到dist的根目录,也就是dist/index.html template: './src/home/index.html', // HTML模板文件路径 minify: { removeComments: true, // 移除注释 collapseWhitespace: true // 合并空格 } }), new HtmlWebpackPlugin({ chunks: ['about', 'vendor', 'styles'], filename: 'about/index.html', // 输出到dist/about/index.html template: './src/about/index.html', minify: { removeComments: true, collapseWhitespace: true } }), new MiniCssPlugin({ filename: 'styles/[name].[contenthash:8].css', chunkFilename: 'styles/[name].[contenthash:8].css' }), new webpack.NamedModulesPlugin(), // 热加载使用 new webpack.HotModuleReplacementPlugin() // 热加载使用 ]
部分示例代码以下:
// src/about/index.js const $ = require('zepto'); require('normalize.css'); require('../common/style.less'); require('./style.less'); $('#about').on('click', function () { alert('点击了about按钮'); });
和传统的JS有点不太同样,多了一些css的require,前面说过,webpack把全部资源当作JS模块,所以这是推荐的作法。
<!--首页--> <!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>首页</title> </head> <body> <ul> <li><a href="/">首页</a> </li> <li><a href="/about">关于</a></li> </ul> <div class="logo"></div> <button id="home">首页按钮</button> </body> </html>
页面中再也不须要编写JS。
注意:html中使用<img />标签导入图片的编译,目前尚未好的解决办法,能够经过css background的形式进行处理
开发模式下直接启用webpack-dev-server便可,会自动加载工做目录下的webpack.config.js
// package.json "scripts": { "build": "webpack", "dev": "webpack-dev-server" }
npm run dev
生产模式下使用webpack编译,编译完成后输出最终文件。
npm run build
├── about │ └── index.html ├── images │ └── logo.b15c113a.png ├── index.html ├── scripts │ ├── about.3fb4aa0f.js │ ├── home.3fb4aa0f.js │ └── vendor:ed5b7d31.js └── styles ├── about.71eb65e9.css ├── home.cd2738e6.css └── vendor.9df34e21.css
项目已经托管到github,有须要的读者能够自取。
https://github.com/xialeistudio/webpack-multipage-example