写一写webpack
学习webapck之前的疑问
npm install webpack webpack-cli
-
webpack是什么时候出现的
因为webpack使用了
ES moudle
模式,大概在12年的时候出现的吧???是为了解决低版本浏览器不支持 ESM 模块化的问题,将各个分散的 JavaScript 模块合并成一个文件,同时将多个 JavaScript 脚本文件合并成一个文件,减少 HTTP 请求的数量,有助于提升页面首次访问的速度。后期 Webpack 乘胜追击,引入了Loader、Plugin
机制,提供了各种构建相关的能力(babel转义、css合并、代码压缩),取代了同期的 Browserify、Gulp。 -
我们为什么要使用webpack,webpack的诞生?
- 因为很久以前,进行网页开发,js实现的逻辑相对比较弱,主要通过面向过程去书写我们的代码,将js代码都放到一个文件中去实现业务逻辑,然后在主入口文件index.html直接引入
- 当逻辑越来越多的时候,js代码都写在一个文件里面,代码会越来越长,变得十分冗余,不利于维护
- 于是我们将面向过程的模式转为面向对象,将不同的功能逻辑写在不同的js文件中,通过script标签引入,哪一部分功能出现问题就针对相应的js文件进行修改,代码维护性大大增强。
- 然后问题又来了,分成这么多js文件,出现了新的问题,首先是文件引入增多,使得页面的加载速度变慢(每个文件都是http请求);二来是看不出代码和文件之间的关系,要想知道一个文件在哪里,需要去看html文件的引入,才知道文件的确切位置;第三是文件的引入加载顺序不能出错,因为后面的文件可能会依赖前面的文件,如果位置颠倒了,会出现以来错误,比如变量未定义或者找不到文件等等
- 接着就有人提出了,利用
ES moudle
模式,在一个文件中引入使用import...from
或者require
的方式将文件都引用到js文件中,这样一来只加载一个文件,网页的速度会很快,其次文件的依赖关系是非明确,再者引入的文件顺序颠倒也没有关系,因为确保了在下面代码饮用前,前面的文件或者模块都加载好了!!!
-
根本上为了解决什么问题???
利于模块化开发
解决浏览器在解析js时存在的盲点-文件模块化
我读webpack4.x文档理解到的
- 管理好模块的依赖关系,有序加载文件顺序,并将把打包好的js自动注入html代码中
- 将图片、css、字体、数据等文件使用loader,管理资源
- 分离公共代码、压缩打包,减少文件体积
- 开发生产两套配置
开发环境:自动热更新、调试输出错误信息文件位置、打包自动清理dist文件夹
生产环境:更大化压缩代码,更小的 bundle,更轻量的 source map,以及更优化的资源
webpack做的工作
- webpack分析得到所有必须模块并合并到数组中
- 提供让这些模块有序、正常的执行环境
webpack概念
基本概念
- 实质:静态模块打包器
- 目的:构建依赖关系图
- 特点:是高度可配置的
核心概念
-
入口:entry
-
入口是用来指示webpack应该使用哪个文件或者模块来作为构建项目中或者代码文件的内部依赖图的开始。是打包代码的入口,而不是代码执行的入口!!!
-
webpack进入入口起点之后,会开始找哪些模块和库是入口起点(直接或者间接)依赖的,然后开始递归构建依赖图,输出到bundles的文件中
-
可指定一个或者多个入口,默认值是./src
-
入口示例
module.exports = { entry: './path/to/my/entry/file.js' }
-
-
出口:output
-
基本上整个应用程序结构,都会被编译到你指定的输出路径中的文件夹中,是静态资源、最终会被html引用的文件
-
出口就是告诉webpack在哪里输出它所创建的bundles,以及如何命名这些文件,默认为./dist
-
代码示例
//webpack.config.js const path = require('path') module.exports = { entry: './path/to/my/entry/file.js', output:{ path:path.resolve(__dirname,'dist'), filename:'my-first-webpack.bundle.js',//'[name]_[hash].js' publicPath:'/'//通常是cdn地址 } }
-
生成bundle的名称和路径
-
考虑到CDN缓存的问题,我们一般会给文件名加上 hash.
-
-
loader:模块的源代码进行转换
-
让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript),loader本身是一个函数,接收源文件作为参数,返回转换后的结果
-
在 webpack 配置中定义 loader 时,要定义在 module.rules 中,而不是 rules
-
在 webpack 的配置中 loader 有两个目标:
-
test 属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件。
-
use 属性,表示进行转换时,应该使用哪个 loader。
-
-
代码示例
//webpack.config.js const path = require('path'); const config = { output: { filename: 'my-first-webpack.bundle.js' }, module: { rules: [ { test: /\.txt$/, use: 'raw-loader' } ] } }; module.exports = config;
“嘿,webpack 编译器,当你碰到「在 require()/import 语句中被解析为 ‘.txt’ 的路径」时,在你对它打包之前,先使用 raw-loader 转换一下。”
-
-
插件:plugins
- 执行比loader更大的任务,比如打包优化和压缩等
- require插件,一般通过option自定义,也可以在一个配置文件中因为不同目的而多次使用同一个插件,这样需要通过new操作来创建实例
名称 | 描述 |
---|---|
CommonsChunkPlugin | 将chunks相同的模块代码提取成公共js |
CleanWebpackPlugin | 在构建之前先清理dist文件夹,清除无用的资源 |
MiniCssExtractPlugin | 将css从bundle文件里提取成一个独立的css文件,替代ExtractTextWebpackPlugin |
HtmlWebpackPlugin | 自动往index.html中注入打包生成的xxx.bundle.js文件,生成一个全新的html文件 |
UglifysWebpackPlugin | 使用uglify-js压缩 JavaScript |
ZipwebpackPlugin | 将打包的出的资源生成一个zip包 |
DefinePlugin | 允许在编译时(compile time)配置的全局常量 |
HotMoudleReplacementPlugin | 启动热替换模块,主要用于本地启动 |
-
模式mode
设置开发development或者生产production其中的一个,启用相应模式下的webpack内置优化
跟着webpack文档走打包路线
起步
-
安装lodash依赖,并在index.js使用import引入,使用npx webpack会将我们的index.js脚本文件作为入口起点,然后输出为bundle.js,我们需要在index.html引用
-
使用一个webpack.config.js配置文件,来管理webpack的配置,把入口文件和输出文件写到配置entry和output中,并使用cli命令去打包
-
配置package.json,在script中添加build使用npm脚本npm run build去启动项目
管理资源
-
加载css:使用style-loader+css-loader加载css样式,打包之后会把样式代码以style标签的形式插入到head头部中,其他样式比如less和sass等会使用less-loader、sass-loader、postcss-loader等
a、style-loader 动态创建 style 标签,将 css 插入到 head 中
b、css-loader 负责处理 @import 等语句
c、postcss-loader 和 autoprefixer,自动生成浏览器兼容性前缀
d、less-loader 负责处理编译 .less 文件,将其转为 css
注意:loader 的执行顺序是从右向左执行的,也就是后面的 loader 先执行 -
加载图片文件和字体文件:使用file-loader和url-loader
a、url-loader 可以使用limit指定在文件大小小于指定的限制时,返回 DataURL,但是有利有弊,如果小于限定值,会将资源转成base64,大于限定值会将资源打包进dist,转成base64的hhtp请求次数会减少,但是会增大文件体积,可能会造成页面渲染变慢。
b、默认情况下,打包之后文件名是文件内容经过md5哈希值加源文件的后缀名,可以通过options设置打包后的文件名
{
test:/\.(png|jpg|gif|svg)$/,
use:[{
loader:'file-loader',
options:{
name:'[name]_[hash:6].[ext]'
}
}]
}
-
加载数据:使用csv-loader和xml-loader
//webpack.config.js const path = require("path") module.exports = { mode: "development", entry: "./src/index.js", output: { filename: "bundle.js", path: path.resolve(__dirname,'dist') }, module:{ rules:[ { test:/\.css$/, use:['style-loader','css-loader'] }, { test:/\.(png|jpg|gif|svg)$/, use:['file-loader'] }, { test:/\.(woff|woff2|eot|ttf|otf)$/, use:['file-loader'] }, { test:/\.(csv|tsv)$/, use:['csv-loader'] }, { test:/\.xml$/, use:['xml-loader'] } ] } }
管理输出
-
分离入口entry,修改output动态生成 bundle 名称;在entry中使用对象的方法设置多个入口,在output中修改filename为:[name].bundle.js,会对应输出入口的bundle文件,然后自己在index.html中手动加入
-
使用
HtmlWebpackPlugin
:自动往index.html中注入打包生成的xxx.bundle.js文件,生成一个全新的html文件 -
使用clean-webpack-plugin:在构建之前先清理dist文件夹,清除无用的资源
- 发生错误:
CleanWebpackPlugin is not a constructor
使用const {CleanWebpackPlugin} = require('clean-webpack-plugin')
引入 - 如果在
plugins
中使用:new CleanWebpackPlugin(['dist'])
出错的话,就改成new CleanWebpackPlugin()
- 发生错误:
-
稍微涉及manifest(webpack-manifest-plugin):通过 manifest,webpack 能够对「你的模块映射到输出 bundle 的过程」保持追踪
//webpack.config.js const path = require("path") const HtmlWebpackPlugin = require('html-webpack-plugin') const {CleanWebpackPlugin} = require('clean-webpack-plugin') module.exports = { entry: { app:"./src/index.js", print:"./src/print.js" }, output: { filename: "[name].bundle.js", path: path.resolve(__dirname,'dist') }, plugins:[ new CleanWebpackPlugin(), new HtmlWebpackPlugin({title:'管理输出'}) ] }
开发环境
-
使用sourceMap:将编译后的代码映射回原始源代码,如果发生错误,sourceMap会明确指出出错文件,在控制台查看可定位出错位置
//webpack.config.js const path = require("path") const HtmlWebpackPlugin = require('html-webpack-plugin') const {CleanWebpackPlugin} = require('clean-webpack-plugin') module.exports = { entry: { app:"./src/index.js", print:"./src/print.js" }, output: { filename: "[name].bundle.js", path: path.resolve(__dirname,'dist'), publicPath:'/' }, plugins:[ new CleanWebpackPlugin(), new HtmlWebpackPlugin({title:'管理输出'}) ], devtool:'inline-source-map',//使用sourceMap //使用webpack-dev-server devServer:{ contentBase:'./dist' } }
-
可能会发生下面的错误,需要限制文件入口体积大小等:控制 webpack 如何通知「资源(asset)和入口起点超过指定文件限制」
-
-
使用开发工具webpack-dev-server,在代码发生变化之后自动编译代码,不用每一次都使用命令编译
- 使用webapck --watch模式,可以监听文件变化自动编译,但是不会自动刷新浏览器,还是需要手动刷新
- 使用webpack-dev-server,代码发生变化时,可以自动编译,编译完成后自动刷新浏览器
- webpack-dev-server提供了简单的web服务器,并且能够实时重新加载,在localhost:8080下建立服务,将dist文件夹目录下的文件作为可访问的文件(代码见上)
-
使用 webpack-dev-middleware
-
它可以把 webpack 处理后的文件传递给一个服务器(server)
-
结合express,使用node来启动
//server.js const express = require('express'); const webpack = require('webpack'); const webpackDevMiddleware = require('webpack-dev-middleware'); const app = express(); const config = require('./webpack.config.js'); const compiler = webpack(config); app.use(webpackDevMiddleware(compiler,{ publicPath:config.output.publicPath })); app.listen(3000,function(){ console.log('example app listening on port 3000!\n') });
-
模块热替换
-
在webpack.config.js配置中require(‘webpack’),因为要用到webapck的一个HotModuleReplacement()
-
在plugins中
new webpack.NamedModulesPlugin(), new webpack.HotModuleReplacement()
-
devServer对象设置属性hot为true
//webpack.config.js const path = require("path") const HtmlWebpackPlugin = require('html-webpack-plugin') const {CleanWebpackPlugin} = require('clean-webpack-plugin') const webpack = require('webpack') module.exports = { entry: { app:"./src/index.js", }, output: { filename: "[name].bundle.js", path: path.resolve(__dirname,'dist'), publicPath:'/' }, plugins:[ new CleanWebpackPlugin(), new HtmlWebpackPlugin({title:'热更新'}), new webpack.NamedModulesPlugin(), new webpack.HotModuleReplacementPlugin() ], devtool:'inline-source-map', devServer:{ contentBase:'./dist', hot:true }, module:{ rules:[ { test:/\.css$/, use:[ 'style-loader','css-loader' ] } ] } }
tree shaking(用于移除未引用代码)
- 将文件标记为无副作用,在package.json中配置属性
"sideEffects": false
( 「副作用」的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。举例说明,例如 polyfill,它影响全局作用域,并且通常不提供 export ),如果有一些代码有副作用,那么将sideEffects设为一个数组,数组为文件路径 - 如果在webpack.config.js中配置模式mode为production的话,会压缩代码
生产环境和开发环境双重配置
-
开发环境和生产环境的构建目标差异很大。在开发环境中,我们需要具有强大的、具有实时重新加载的/热模块替换能力的source map和localhost server。在生产环境中,我们的目标则转向于关注更小的bundle,更轻量级的source map以及更优化的资源,以改善加载时间
-
安装
webpack-merge
,避免在生产中使用 inline-*** 和 eval-***,因为它们可以增加 bundle 大小,并降低整体性能 -
指定环境:
process.env.NODE_ENV
是一个环境变量,在代码src文件夹下面的本地代码都可以引用到。在配置文件中,比如使用new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') })
一些库会根据环境进行代码优化,比如开发会添加额外的日志记录和测试,生产会删除或者添加一些重要的代码
4.mode
设置
-
development
:将process.env.NODE_ENV
的值设置为development
,启用NamedChunksPlugin
和NamedModulesPlugin
-
production
:将process.env.NODE_ENV
的值设置为production
,启用FlagDependencyUsagePlugin
,FlagIncludedChunksPlugin
,ModuleConcatenationPlugin
,NoEmitOnErrorsPlugin
,OccurrenceOrderPlugin
,SideEffectsFlagPlugin
和UglifyJsPlugin
//webpack.common.js const path = require('path') const {CleanWebpackPlugin} = require('clean-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { entry:{ app:'./src/index.js' }, output:{ filename:'[name].bundle.js', path:path.resolve(__dirname,'dist') }, plugins:[ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ title:'production' }) ] }
//webpack.dev.js const merge = require('webpack-merge') const common = require('./webapck.common.js') module.exports = merge(common,{ devtool:'inline-source-map', devServer:{ contentBase:'./dist' } })
//webpack.prop.js const webpack = require('webpack') const merge = require('webpack-merge') const UglifyJSPlugin = require('uglifyjs-webpack-plugin') const common = require('./webapck.common.js') module.exports = { // plugins:[ // new UglifyJSPlugin() // ] devtool: 'source-map', plugins:[ new UglifyJSPlugin({ sourceMap:true }), new Webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }) ] }
代码分离
-
入口起点:使用entry配置手动分离代码
- 、如果两个入口chunk都引入了重复的代码,那么重复的模块都会打包进bundle中,体积增大
- 这种方法不够灵活,并且不能将核心应用程序逻辑进行动态拆分代码,不知道这一点吼吼吼
-
防止重复,使用CommonChunkPlugin去重和分离chunk
-
webpack4已经删除了CommonChunkPlugin,使用的话会报错的
-
webpack4已经改成了这样
const path = require("path") const HtmlWebpackPlugin = require('html-webpack-plugin')//自动往html文件插入script标签,引入打包生成的bundle const {CleanWebpackPlugin} = require('clean-webpack-plugin')//在打包之前,清除dist的文件 const webpack = require('webpack') module.exports = { entry: { app:"./src/index.js", anthor:"./src/anthor_module.js" }, output: { filename: "[name].bundle.js", path: path.resolve(__dirname,'dist') }, plugins:[ new CleanWebpackPlugin(), new HtmlWebpackPlugin({title:'代码分离'}), //webpack4 移除了 CommonsChunkPlugin // new webpack.optimize.CommonsChunkPlugin({ // name:'common'//指定公共bundle的名称 // }) ], mode:'development', /*代码分离改成了下面这样了*/ optimization:{ // runtimeChunk:{ // name: 'manifest' // }, splitChunks:{ cacheGroups:{ commons:{ chunks:'initial',//为什么要加这个才可以分离代码 name:'common', minChunks:2, minSize:0 }, vendor:{ chunks:'initial', test:'/node_modules/', name:'vender', priority:10 } } } } }
-
-
动态引入:通过模块的内联函数调用来分离代码
懒加载
不要一开始就import, 因为import就直接加载了,比如点击一个按钮的时候才会要加载资源去,那就点击的时候再去import,如果一直不执行点击事件就不加载
缓存
通过必要的配置,确保webpack编译生成的文件能够被客户端缓存,而在文件内容变化后能够请求到新的文件