1. webpack和webpack-cli的安装
cnpm install webpack webpack-cli -g
// webpack -v 检查版本
// webpack-cli -v
全局安装有一个缺点,比如你有一个项目时用webpack4打包的,它依赖的一个项目使用webpack3打包的,这时如果你是全局安装的webpack4,就不能将项目运行起来。怎么办呢?
卸载全局的webpack 和webpack-cli
cnpm uninstall webpack webpack-cli -g
然后我们只在我们的项目中安装需要的版本,进入项目目录,执行:
cnpm install webpack webpack-cli -D
//等价于
cnpm install webpack webpack-cli --save-dev
这时我们在项目目录下执行webpack -v
是找不到这个命令,需要用npx webpack -v
,这时才会显示版本号。
那我们怎么安装指定的版本呢,以及怎么知道webpack都有哪些版本呢?
cnpm info webpack
查到现有版本后,我们就可以按需安装了
cnpm install webpack@3.12.0 webpack-cli -D
这样我们就给项目安装了webpack3了
2. 更改webpack默认配置文件
webpack的默认配置文件是webpack.config.js
,那么如果我们自定义一个文件叫webpack.js,想让它作为配置文件,怎么办呢?npx webpack --config webpack.js
执行这个命令可以改变。
3. 更改默认的命令方式
我们在打包时用的默认命令是npx webpack
,我们还可以自定义命令,比如在package.json
文件中scripts
标签里配置:
"scripts": {
"build": "webpack"
}
这样我们再运行bundle命令,就会帮我们打包了,命令是npm run build
4. 一个简单的webpack.config.js如下
const path = require('path');
module.exports = {
mode:'production', //development
entry:'./src/index.js',
output:{
filename:'main.js',
path:path.resolve(__dirname,'dist');
}
}
//production 环境输出的是压缩的代码,而development 不压缩
5. 文件打包
我在index.js文件引入一个图片import logo from '.src/logo.png'
,这是我们再打包,发现报错,因为webpack默认只能打包js文件,其他的文件需要其他的loader来处理。
所以我们安装file-loadercnpm install file-loader -D
,然后在webpack.config.js中配置。
const path = require('path');
module.exports = {
mode:'development', //production
entry:'./src/index.js',
output:{
filename:'main.js',
path:path.resolve(__dirname,'dist');
},
module:{
rules:[{
test:/\.png$/,
use:{
loader:'file-loader'
}
}]
}
}
6.怎样让打包出的文件名不变
比如我们打包一个logo.png的图片,然后在dist目录下,你发现图片的名字是一长串的字符,那怎样让图片的名字不变呢,我们可以继续在webpack.config.js中配置options。
const path = require('path');
module.exports = {
mode:'development', //production
entry:'./src/index.js',
output:{
filename:'main.js',
path:path.resolve(__dirname,'dist');
},
module:{
rules:[{
test:/\.png$/, // /\.(png|jpg|gif)$/ 多重选择
use:{
loader:'file-loader',
options:{
name:'[name].[ext]'
}
}
}]
}
}
7. 怎样让打包出的图片在一个单独文件夹
比如想让打包出的图片在dist/images/目录下,怎么配置呢?
const path = require('path');
module.exports = {
mode:'development', //production
entry:'./src/index.js',
output:{
filename:'main.js',
path:path.resolve(__dirname,'dist');
},
module:{
rules:[{
test:/\.png$/, // /\.(png|jpg|gif)$/ 多重选择
use:{
loader:'file-loader',
options:{
name:'[name].[ext]',
outputPath:'/images'
}
}
}]
}
}
8. 认识url-loader
首先要知道url-loader和file-loader有相同的功能,它也能打包文件。但是它打包的文件是以base64的形式存在js文件中的,这种方式有优点也有缺点:
优点:
如果文件比较小,比如一张图片5kb,打包后这个图片以base64的形式存在js中,就会加载js文件时候,一起把图片加载进来了,省去一次单独的请求。整体加载速度更快了。
缺点:
如果一个文件比较大,那么打包后还是以base64的形式存在js中,就会导致这个js文件比较大,直接导致加载js速度比较慢。
所以如果有一个配置项,比如文件的临界大小是10240字节(10kb),大于这个值我们单独打包出来放到一个文件目录中,比如/images。如果文件大小小于这个值,就直接打包到js文件中,是不是就完美了。
const path = require('path');
module.exports = {
mode:'development', //production
entry:'./src/index.js',
output:{
filename:'main.js',
path:path.resolve(__dirname,'dist');
},
module:{
rules:[{
test:/\.png$/, // /\.(png|jpg|gif)$/ 多重选择
use:{
loader:'url-loader',
options:{
name:'[name].[ext]',
outputPath:'/images',
limit:10240 //大于单独打包到文件,小于以base64打包到js文件
}
}
}]
}
}
别忘了前提安装url-loadercnpm install url-loader -D
9. 打包css文件
打包css文件,就需要用到style-loader和css-loader
cnpm install style-loader css-loader -D
然后在rules里面加入
{
test:/\.css$/,
use:['style-loader','css-loader']
}
10. 打包scss文件
打包scss文件需要用到node-sass和sass-loader
cnpm install node-sass sass-loader -D
然后在rules里面加入
{
test:/\.scss$/,
use:['style-loader','css-loader','sass-loader']
}
注意:如果用的多个loader,执行顺序从下到上,从右到左,比如上面的就是先用sass-loader将scss文件解析成css文件,然后用css-loader打包css文件,最后用style-loader将样式挂载到head标签的style标签中。
11.认识postcss-loader
我们在写一些样式的时候要考虑兼容性问题,比如
transform:translate(100px,100px)
这些样式一般都要加厂商前缀来兼容,但是自己写又比较麻烦,有没有一个工具可以帮助我们写,这里就用到postcss-loader,首先还是安装:
cnpm install postcss-loader -D
然后需要新建一个postcss.config.js
文件,里面写一些配置:
module.exports = {
plugins: [
require('autoprefixer')
]
}
这里需要用到一个插件:
cnpm install autoprefixer -D
12. css-loader一些其他配置
{
test:/\.scss$/,
use:['style-loader','css-loader','sass-loader','postcss-loader']
}
上面是我们打包的scss文件的配置,打包顺序是postcss-loader先加前缀,sass-loader解析成css,css合并,然后挂载到head中的style标签。
但是如果我一个scss文件中,上面依赖其他的scss文件,比如这样:
@import './a.scss'
body{
#root{
width:100px;
height:100px;
background:red;
}
}
如果像上面那样写配置,a.scss文件可能就不会被先post,然后在sass,所以为了让a.scss也走相同的解析顺序。我们要这样配置:
{
test:/\.scss$/,
use:[
'style-loader',
{
loader:'css-loader',
options:{
importLoaders:2
}
},
'sass-loader',
'postcss-loader'
]
}
还有一个配置,比如一个js文件导入了一个scss文件@important './index.scss'
,这个js文件的代码就是创建一个div挂载到body上,这个js文件还引用了另一个js文件,里面的代码也是创建一个div。并通过img.classList.add()
的方式添加样式,上面的导入方式是全局导入的方式,改变scss中的代码可能会改变很多文件的样式。那么我们想让某个scss文件只作用于某个文件怎么做呢?
首先导入方式更改为:@important style from './index.scss';
然后需要添加css模块化属性:
{
loader:'css-loader',
options:{
importLoaders:2,
modules:true //css模块化
}
}
13. 怎样打包图标字体文件
从iconfont网站找到自己想要的图标,下载下来,把.eot .svg .ttf .woff的字体文件放到font文件夹下,把iconfont.css中的代码拷贝到自己的样式代码中。
<div class='iconfont iconfont-market'></div>
这样就会显示图标了
配置中这样配置:
{
test:/\.(eot|svg|ttf|woff)$/,
use:{loader:'file-loader'}
}
14.认识Html-Webpack-Plugin
安装cnpm install html-webpack-plugin -D
作用:HtmlWebpackPlugin会在打包结束后,自动生成一个html文件,并把打包生成的js自动引入到这个html文件中。
进行配置:
const HtmlWebpackPlugin = require('html-webpack-plugin'); //首先要引入
plugins:[
new HtmlWebpackPlugin()
]
怎样让生成的html文件有固定的元素呢,那就需要一个模板,我们可以在src下新建我们的模板template.html文件,然后做如下配置:
const HtmlWebpackPlugin = require('html-webpack-plugin'); //首先要引入
plugins:[
new HtmlWebpackPlugin({
template:'./src/template.html'
})
]
这样打包生成的html文件就定义的模板html一样了。
15.认识CleanWebpackPlugin
安装:cnpm install clean-webpack-plugin
作用:每次执行打包命令后,都会先将原来打包的dist删除,它不是webpack官方的plugin。
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
plugins: [
new CleanWebpackPlugin({
try: false,
watch: false
}),
]
16. 同一个入口文件,生成不同的文件名
entry:{
main:'./src/index.js',
sub:'./src/index.js'
}
output:{
publicPath:'http://cdn.com.cn', //注入打包后的html中的script标签前缀
filename:'[name].js',
path:path.resolve(__dirname,'dist')
}
17.source-map的作用
module.exports = {
mode:'development', //开发模式下是打开了devtool:source-map的
devtool:'none',
entry:{},
output:{}
}
当设置devtool:'none'
时,如果js中的代码有问题,在控制台显示的位置是在打包后的文件中的,这样定位错误非常不方便,如果我们想让错误定位在原始的js代码中,就需要设置devtool:'source-map'
devtool有很多值可以选,建议开发中使用devtool:'cheap-module-eval-source-map'
这种方式打包速度快,错误提示全。
如果是线上环境建议使用:devtool:'cheap-module-source-map'
18. WebpackDevServer提升开发效率
每次打包都要运行npm run build
,然后在到dist目录下打开index.html
才能看到改动的效果,过程有点麻烦。那有没有方法,可以监听我的源代码改变,我的源代码一改变,就自动打包,然后我们刷新页面就可以看到效果了。方法有2种:
第一种:改变package.json中的配置:
"scripts": {
"build": "webpack --watch"
}
第二种:devserver可以感知我们的源代码改变,并自动打开刷新浏览器
安装:cnpm install webpack-dev-server -D
package.json
中加入运行命令:
"scripts": {
"build": "webpack --watch",
"devserver":"webpack-dev-server"
}
在webpack.config.js
配置:
module.exports = {
mode:'development', //开发模式下是打开了devtool:source-map的
entry:{},
output:{},
devServer:{
contentBase:'./dist',
open:true
}
}
用devserver解决开发阶段的跨域问题:
devServer:{
contentBase:'./dist',
open:true,
proxy:{
'/api':'http://zhangsan.com'
}
}
19. 认识HotModuleReplacement 热模块更新
需求场景:
有一个按钮点击后页面就增加一个div元素,第偶数个元素的背景是有颜色的。这时我把背景色改成蓝色,想看下是否起作用了。由于页面刷新后,现在页面只有一个增加按钮,我需要再点击增加出许多元素,才能发现我改的颜色是否对的。
我的理想状态是页面元素个数不变,只有颜色做更新,也就是我需要的效果更新,其他的不更新。怎么办呢?
这就是要说的热模块更新:
首页在webpack.config.js中导入webpack,因为我们一会要用到webpack的一个插件。
cont webpack = require('webpack');
然后在devServer中开启热模块更新:
devServer:{
contentBase:'./dist',
open:true,
proxy:{
'/api':'http://zhangsan.com'
},
hot:true,
hotOnly:true //可加可不加,加表示热更新可能失效,但是此时浏览器也会帮你更新下,这是hotOnly就是说如果失效就失效,也别刷新。
}
然后添加热模块更新的插件:
plugins:[
new webpack.HotModuleReplacementPlugin()
]
这时我们只对样式文件做更改就可以了。
还有一种情况,比如一个a.js文件导入了b.js 和 c.js文件,b的js代码就是展示一个数,点击自增1。而c的js就展示一个数字。
这时我点击11,数字开始增大,比如增加到19。然后我去c.js把200改成300,你会发现页面整体刷新,上面数组19回到原始值11。
这就是热更新中的问题,我只想更新下面的数字,不想刷新上面的数字,怎么办呢?
首先上面的热更新配置到配置好后,需要在a.js写如下代码:
import number1 from './b.js'; //里面是一个方法,然后export default 导出
import number2 from './c.js';
number1(); //显示数组11
number2(); //显示数字200
if(module.hot){
module.hot.accept('./c.js',()=>{
//处理一些其他逻辑
//更新
number2();
});
}
20. Webpack中用babel处理es6语法
cnpm install babel-loader @babel/core -D
rules中加入下面规则:
{
test:/\.js$/,
exclude:/node_modules/,
loader:'babel-loader'
}
然后在安装:
cnpm install @babel/preset-env -D
babel-loader
只是webpack和babel一个桥梁,有了它他们就算认识了,但真正处理es6语法的是babel/preset-env
,它里面涵盖了转化es6的一些规则。
然后我们需要再配置:
{
test:/\.js$/,
exclude:/node_modules/,
loader:'babel-loader',
options:{
presets:['@babel/preset-env']
}
}
这样配置之后,一些es6语法可以被转换成es5,比如const转成var,箭头函数转换成普通方法。但是比如new Promise()
有些浏览器就没有,我们怎么用es6呢,我们需要用到babel-polyfill
来补充这些缺失。
cnpm install @babel/polyfill -D
然后在每个用到es6的js文件顶部引入:import '@babel-polyfill;'
但是这样引入后,打包后你会发现main.js的大小变大了很多,因为babel-polyfill
实现了es6中缺失的东西,比如promise,比如arr.map()函数,他会用代码实现一遍。那这样全部实现导致main.js变大,有没有一个方法,让babel-polyfill只实现我文件用到的es6语法,没用到的就不用实现,也就是按需实现,就会大大减小main.js文件的大小。
没错,这是可以实现的,配置如下:
{
test:/\.js$/,
exclude:/node_modules/,
loader:'babel-loader',
options:{
presets:[['@babel/preset-env',{
useBuiltIns:'usage'
}]]
}
}
babel/preset-env还有其他许多设置,比如target属性:
{
test:/\.js$/,
exclude:/node_modules/,
loader:'babel-loader',
options:{
presets:[['@babel/preset-env',{
useBuiltIns:'usage',
targets:{
chrome:'67'
}
}]]
}
}
这表示我运行的环境是chrome 67的版本,webpack会判断chrome 67已经完全兼容es6语法了,就不会做任何处理了。
有时options
里面的内容非常多,我们可以把options的内容单独拿出了,放到一个文件中,我们新建一个文件叫 .babelrc
,然后我们把options的内容搬过来:
{
presets:[['@babel/preset-env',{
useBuiltIns:'usage',
targets:{
chrome:'67'
}
}
然后webpack.config.js
文件里面的内容就剩这样了:
{
test:/\.js$/,
exclude:/node_modules/,
loader:'babel-loader'
}