webpack学习笔记(2)

安装开发服务器webpack-dev-server
避免开发过程中重复的打包操作,自动编译打包、自动刷新浏览器,开发者只需写好代码就可以。
特点:将编译好的文件保存在内存中。

npm i webpack-dev-server -D
webpack.config.js中的配置:

devServer:{
    //为哪个文件夹提供此服务
    contentBase:'rsolve(__dirname,'dist')',
    //页面实时刷新
    inline:true,
    //启动gzip压缩
    compress:true
    //自动打开浏览器
    //open:true
}

启动:本地下载的话可以使用npx webpack-dev-serve命令启动这个服务或者在package.json文件中进行配置,然后直接使用npm run dev命令启动服务。成功启动后,直接点击http://localhost:8080进行调试。

"scripts":{
  "dev":"webpack-dev-server"
}

npm run dev——>执行这个命令,优先从本地中查找有没有安装webpack-dev-server包。
有了这个命令后,在开发中就不用频繁的去执行npm run build命令打包了。npm run dev会自动更新改动的地方,等开发结束后,执行npm run build最终打包即可,打包结束后,把dist文件夹放到服务器进行部署。


提取css文件为单独文件(生产环境下配置)
使用css-loader能将css文件变成commonJS模块内嵌到JS文件中,这样操作会使得js文件体积变大,加载js文件的速度降低,为了优化速度,可以将内嵌在js中的css提取出来,提取出来的css文件就可以和js文件并行加载。提取需要安装mini-css-extract-plugin插件。提取出来的css文件会以<link>标签的形式添加到document中。
使用这个插件就不需要使用style-loader包了。

use:[
    MiniCssExtractPlugin.loader,
    'css-loader'
]
plugins:[
    new MiniCssExtractPlugin({
        //对提起的css文件重命名
        filename:'css/index.css'
})
]

css文件的兼容性处理(生产环境下配置)
需要用到postcss库---->此库要在webpack中使用,需要安装postcss-loader以及插件postcss-preset-env。
压缩css文件(生产环境下配置)
使用optimize-css-access-webpack-plugin插件
压缩js文件(生产环境下配置)
只要把webpack.config.js中的mode改为production即可,在生产环境下,会自动启动很多插件,其中UglyfyJsPlugin插件会直接对js文件压缩,不用下载压缩包。
压缩html文件(生产环境下配置)
也不需要下载什么压缩包,在plugins配置即可。

        new HtmlWebpackPlugin({
            template:'./src/index.html',
            //压缩html,不需要下载插件
            minify:{
                //清除空格
                collapseWhitespace:true,
                //清除注释
                removeComments:true
            }
      })

es6转为es5的处理(生产环境下配置)
部分低版本浏览器不认识es6语法,比如let/const关键字,箭头函数等,需要转为浏览器能识别的es5。
需要下载babel-loader @babel/core @babel-preset-env

//第一种配置方法
//这种配置只能做基本的转换,es6中的promise对象还是不能转换
 {
                test:/\.js$/,
                exclude:/node_modules/,//不对node_modules里的js文件匹配处理
                use:{
                    loader:'babel-loader',
                    options:{
                        //preset:指示babel做什么样的转换处理
                        preset:['@babel/preset-env']
                    }
                }
            }
//第二种配置方法
//安装@babel/polyfill,能把全部的es6语法转为es5语法
//安装好后,在index.js文件中引入'import @babel/polyfill'

//缺点:转换后的bundle.js文件变得很大,因为里面包含了所有的方法,有些方法根本没用到,也一起打包进去。
//第三种配置方法(最好)
//需要转换的就去转换,不需要的就不做转换,按需转换。
//安装core-js   npm i core-js -D

         use:{
                    loader:'babel-loader',
                    options:{
                        //preset:指示babel做什么样的转换处理
                        preset:[
                            [
                                '@babel/preset-env',
                                {
                                    useBuiltIns:'usage',//按需加载
                                    corejs:{ //指定corejs版本
                                        version:3
                                    },
                                    targets:{ //指定应用到哪些版本的浏览器
                                        chrome:'60',
                                        firefox:'60',
                                        ie:'9',
                                        safari:'10',
                                        edge:'17'
                                    }
                                }
                            ]
                        ]
                    }
               }

webpack开发环境下的性能优化(使用HMR、source map)

HMR(hot module replacement,热模块替换)
在开发阶段,当对某个模块作出修改后,在控制台会看到其它模块的结果也会跟着一起刷新,虽然其它模块并没做任何改动。这显然不是我们想要的结果,我们希望的是哪个模块修改了,只刷新这个模块的值,其它模块不做处理。所以就有了HMR。
HMR是加快开发阶段开发速度,节省开发时间的一种方式。
HMR能实现只对单个模块的刷新,而不用重新刷新页面中的所有模块。(有点类似ajax技术,只刷新局部页面)
配置:

devServer:{
//webpack.config.js中的配置修改后,需要重启webpack-dev-server
    hot:true
}

配置好HMR后,分别对css文件、js文件、HTML文件进行测试。在webpack-dev-server下,发现当单独对css文件修改时,控制台提示Nothing hot updated.。无论修改什么样式,这些样式都不会生效。目前还找不到原因。
单独对js文件修改时,发现HMR功能无效;单独修改HTML文件时,也发现HMR功能无效,并且HTML文件无法更新。

//js文件默认不能使用HMR功能,要使用必须添加支持HMR的代码
//且HMR只能处理非index.js的文件,对于入口文件index.js无法处理
//在index.js文件中写上下面的配置代码:
if(module.hot){ //
    module.hot.accept('./print.js',function(){
        //监听print.js文件的变化,如若变化,打包此文件,并执行回调函数
        print()
    })
}

//对于HTML文件,需要修改entry
entry:['./src/index.js','./src/index.html'],
//修改entry后测试,HTML页面能得到更新,但还是无法使用HMR功能。不过由于HTML文件只有一个,所以,没有HMR功能也无妨。

source map
当使用webpack打包源代码时,可能很难发现出错误的地方在源代码中的位置。为了能更好地追踪错误,提供了source map功能,将构建后出错的代码映射到源代码的位置。
配置很简单:一行代码。但是source map有很多种不同的选项供选择。

devtool:'source-map' //外部,直接使用source-map,在dist/js文件夹下会生成一个.map文件
      - 生成的source-map能映射到源代码出错的具体位置
devtool:'inline-source-map' //生成的sourcemap文件嵌套在bundle.js文件中,所以也称为内联sourcemap。
    - bundle.js文件中只有一个sourcemap
    - 映射功能和上面的source-map一致
devtool:'hidden-source-map'//外部,在dist/js文件夹内会生成一个外部的.map文件
    - 能提示错误的原因,但是不会映射到源代码的位置,而是映射到构建后代码的位置。
devtool:'eval-source-map'//内联,没有生成外部文件,生成的sourcemap文件嵌套在bundle.js文件中。
    - 每一个文件(css、js、less...)都会生成对应的source-map在bundle.js文件中
    - 和source-map、inline-source-map一样,能映射到源代码的具体位置
devtool:'nosource-source-map'//外部
    - 能提示错误原因,以及源代码哪行代码出了问题,但是不会显示源代码信息
devtool:'cheap-source-map'//外部
    - 能提示错误原因,也能映射到源代码出错位置。不同的是,它只有行映射,没有列映射,即你知道这行代码出了问题,但是不清楚这行代码的哪个位置出了问题。
devtool:'cheap-module-source-map'//外部
    - 同cheap-source-map
内联和外部的区别:
1.外部生成的文件,内联没有
2.内联构建速度比外部快

开发环境的选择:选择速度快,调试友好的
速度快(eval>inline>cheap>...)
- eval-source-map
- eval-cheap-source-map
调试更友好
- source-map(能精确到行、列)
- cheap-module-source-map
- cheap-source-map(只能精确到行)

综合选择:eval-source-map

生产环境的选择:源代码要不要隐藏?调试要不要更友好?
生产环境不考虑内联的source-map,因为会让bundle.js文件体积变大。所以,inline-source-map、eval-source-map这两种方式排除。
代码若要隐藏:
hidden-source-map、nosource-source-map
调试更友好:
source-map(能精确到行、列)、cheap-module-source-map

webpack生产环境下的性能优化

1、oneOf
在module中配置了很多的loader,某文件在处理过程中会去寻找指定的loader处理,但处理完后,并不会停止,还会去匹配其它的loader,直到全部匹配完,很显然,后续的操作都是无意义的,都是在浪费性能。
所以,oneOf的作用就是:根据文件类型匹配对应的loader转换即可。
配置:
把所有的loader配置放在oneOf中即可。注意:在oneOf中不能有2个loader处理同一个文件类型,必须分离开。

module:{
        rules:[
            {
                test:/\.js$/,
                exclude:/node_modules/,
                // //优先执行
                enforce:'pre',
                loader:'eslint-loader',
                options:{
                    fix:true
                }
            },
            {
                //下面的loader只会匹配一个,处理性能更好
                //注意:不能有两个配置处理同一种类型文件
                oneOf:[
                    {
                        test:/\.css$/,
                        use:[...commenCssLoader]
                    },
                    {
                        test:/\.less$/,
                        use:[...commenCssLoader,'less-loader']
                    },
                ]
            }
        ]
    },

2、缓存
对资源做缓存处理,等用户下次请求的时候就可以直接从缓存中拿取,速度会快很多。浏览器缓存可以降低网络流量,加快网站的访问速度。
但是,缓存也有弊端。当把文件全部部署到服务器上,之后如果没有更改文件名,浏览器会认为此文件没更新,就会使用此资源的缓存版本。由于缓存的存在,可能获取不到最新的文件。
babel缓存:
babel能对js代码进行处理,编译成浏览器能够识别的语法。babel也能对众多的js文件进行处理,将它们缓存起来,如果修改了哪份js文件,打包后,只会请求这一份js文件,其它文件依然是从缓存中获取。
配置:https://www.bilibili.com/video/BV1e7411j7T5?p=23
文件资源缓存:

/*在强制缓存期间,服务器是不会向浏览器返回请求资源的,而是让浏览器直接从缓存中拿取资源。这样有个问题,假如在强制缓存期间,某些文件代码发生了变化,那就无法获取到最新的页面数据。
可以给请求的资源,比如css文件、js文件的名称加个版本号,如果文件更新了,会有个更新的版本号。
这个版本号怎么加上去?用hash表示。
output:{
    filename:'js/[name].[hash:8].js',
    path:resolve(__dirname,'dist')
}
new MiniCssExtractPlugin({
    //对提取的css文件重命名
    filename:'css/index.[hash:8].css'
}),
在输出的css文件、js文件名称上就能看到hash结尾,在控制台也能看到这两种文件就不再是memory cache了
css文件、js文件每次修改打包后,都会生成新的hash值
但是又带来个问题,因为css和js文件都使用了hash,即使我只修改了其中一个文件,打包后,另一个文件的hash值也会跟着变化,因为它们共用一个hash,所以导致另一个文件也重新发送了请求,可事实是另一个文件根本没有做任何修改。
换成chunkhash:如果打包的两个文件来自同一个chunk,那么它们的hash就一样,否则不一样。
试试。
实际结果不如人意,css文件和js文件的hash值仍一样,因为webpack打包过程中将css以commonJS规范编译引入到了js文件中,所以它俩来自同一个chunk。
换成contenthash:根据文件内容生成的hash,文件内容不一样,hash肯定不一样
试试。
两种文件的hash不一样了,当修改css文件的内容,再打包后,css文件重新发送请求,js文件的hash不变,不会重新发送请求,而是使用上次的缓存了*/
new MiniCssExtractPlugin({
    //对提取的css文件重命名
    filename:'css/index.[contenthash:8].css'
}),

tree shaking:树摇
你可以将应用程序想象成一棵树。绿色表示实际用到的源码和 library,是树上活的树叶。灰色表示无用的代码,是秋天树上枯萎的树叶。为了除去死去的树叶,你必须摇动这棵树,使它们落下。
具体实践:

//创建了一个print.js,文件中包含两个方法
export function sum(x,y){
    console.log(x+y)
}

export function mul(x,y){
    console.log(x*y)
}
//在index.js文件中引入其中的sum()方法,不引入mul()方法
//既然mul()没被导入,它就是“未引用代码(dead code)”,这种代码应该被删除的
import {sum} from './print.js'
sum(3,3)
//在package.json中配置:
"sideEffects":false //标记所有的文件都是无副作用的,可以删除那些未被使用的代码
//如果有些代码有副作用,就以数组的形式把代码加进去。
"sideEffects":[
      ./src/some-side-effectful-file.js
]
//配置了sideEffects后,发现css文件在tree shaking后无意被删除了,因为css文件都是直接引入,没有执行什么操作。
//所以,为了避免被删除,需要把css文件添加到sideEffects列表中
"sideEffects":[
      ./src/some-side-effectful-file.js,
      *.css,
      *.less
]

tree shaking要能生效,必须是在mode:'production'环境下。
4、代码分割(只针对js文件)
未使用代码分割的情况下,所有的js文件都是打包到了同一个文件中,导致代码体积很大。代码分割将一个打包后的大文件,分成几个独立的小文件。可以提高文件加载速度、实现文件按需加载功能。
分两种情况:
第一种:将单入口拆分成多入口,一个入口形成一个chunk代码块,然后输出对应的文件

//未拆分前是单入口
 entry:['./src/index.js'],
//拆分成多入口---->打包后,就会输出两个对应的bundle.js
//index.js中导入test.js测试没问题
 entry:{
    index:'./src/index.js',
    test:'./src/test.js'
},
 output:{
        filename:'js/[name].[contenthash:8].js',
        path:resolve(__dirname,'dist')
    }

执行npm run build命令后,查看dist/js下的js文件,出现了两个js文件,说明两个文件是分开打包的。
test.js文件

但是修改单入口为多入口这种方式并不方便,所以有了第二种途径。

//在webpack.config.js中加上如下配置(单页面情况)。
 entry:'./src/index.js',
 output:{
     filename:'js/[name].[contenthash:8].js',
     path:resolve(__dirname,'dist')
 },
optimization:{
      splitChunks:{
          chunks:'all'
    }
}

optimization的作用:
如果是单页面应用,只有一个entry,即都是从index.js作为入口,如果index.js中引入了第三方库,optimization可以把这个第三方包单独打包成一个chunk输出,就不会打包到bundle.js中去。
如果是多页面应用,即entry中有多个入口(index.js、test.js),它会分析这些文件有没有使用相同的依赖,比如有没有都用到jQuery,如果都用到了jQuery,就会把jQuery提取成单独的一个chunk,避免jQuery被打包两次。
注意:optimization只是对第三方包起作用。如果不同的js文件还是打包到一个bundle.js文件中,想要把不同的js文件分开打包,此方式不可行
如果我想把index.js和test.js文件分开打包,该怎么实现呢?
import()
在index.js文件中使用import()动态导入语法,可以对指定的js文件单独打包。

/*webpackChunkName:'test'*/:给打包后的js文件加上test名称
import(/*webpackChunkName:'test'*/'./test.js')
    .then((result)=>{ //result:是个module对象
        console.log(result)//如下图
        result.mul(2,3)//6
    })
    .catch(()=>{
        console.log('打包失败')
    })


5、懒加载和预加载
懒加载:文件需要使用的时候才加载。比如执行点击事件,再去异步加载代码,可以提高代码利用率。
预加载:等其它资源加载完毕了,浏览器空闲的时候再去偷偷加载。

使用index.js和test.js示例。

//index.js
import { mul } from './test.js'

console.log('我是index.js文件')

//文件懒加载+预加载
document.getElementById('btn').addEventListener('click',()=>{ //点击按钮的时候才去加载test.js文件,不要一打开页面就加载test.js
    import(/*webpackChunkName:'test',webpackPrefetch:true*/'./test.js') 
        .then((result)=>{ //result:是个module对象
            result.mul(2,3)
        })
        .catch(()=>{
            console.log('打包失败')
        })
})

//引入css文件
import './index.css'
//引入less文件
import './index.less'

//js文件默认不能使用HMR功能,要使用必须添加支持HMR的代码
//且HMR只能处理非index.js的文件,对于入口文件index.js无法处理
if(module.hot){ //
    module.hot.accept('./print.js',function(){
        //监听print.js文件的变化,如若变化,打包此文件,并执行回调函数
        print()
    })
}
//test.js
console.log('我是test.js文件')
export function sum(x,y){
    console.log(x+y)
}

export function mul(x,y){
    console.log(x*y)
}

6、externals
作用:拒绝某些包被打包,比如通过import $ from 'jQuery',这些包的体积很大,在js文件打包的时候如果将它们也打包进去,到时候加载速度肯定会降低。

 externals:{
      jquery:'jQuery'
  }

7.dll(配置、理解上有点难)
dll将引入的第三方库(jQuery、React、Vue)单独拆开来,打包成不同的文件,有利于性能优化。
7.1 创建一个webpack.dll.js文件
先打包jQuery,打包后的文件名称为jquery.js,这个文件中以[name][hash:8]的名称向外暴露了jQuery第三方库的内容。
plugin的插件会生成manifest.json文件,文件中以"name":"[name]
[hash:8]"建立了和jQuery的映射关系。使用dll打包后,将来就不要再打包了,直接引入打包后的jQuery文件即可。

const {resolve} = require('path')
const webpack = require('webpack')
module.exports = {
    entry:{
//jquery:表示打包后的文件名称是jquery
//['jQuery']:表示要打包的是jQuery这个库
       jquery:['jQuery']
    },
    output:{
        filename:'[name].js',
        path:resolve(__dirname,'dll'),
        library:'[name]_[hash:8]'//打包后的库向外暴露的内容名称
    },
plugins:[
    new webpack.DllPlugin({
        name:'[name].[hash:8]',//打包后的库向外暴露的内容名称
        path:resolve(__dirname,'dll/manifest.json')//输出文件路径
//manifest.json的作用:提供和jQuery的映射关系,通过
  })
],
mode:'production'
}
注意:运行webpack的时候,默认查找webpack.config.js文件,为了能执行webpack.dll.js文件,需要执行webpack --config webpack.dll.js

7.2 dll将第三方库打包好后,回到webpack.config.js配置文件

const webpack = require('webpack')
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
plugins:[
    new HtmlWebpackPlugin({
      template:'./src/index.html'
}),
//告诉webpack不用打包jQuery了
    new webpack.DllReferencePlugin({
      manifest:resolve(__dirname,'dll/manifest.json')
    }),
//将jquery.js文件自动引入到html文件中去,这样就能使用jQuery了
    new AddAssetHtmlWebpackPlugin ({
      filepath:resolve(__dirname,'dll/jquery.js')
  })
],
mode:'production'
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342