webpack基本用法及原理(10000+)
1 webpack是什么
所有工具的出现,都是为了解决特定的问题,那么前端熟悉的webpack是为了解决什么问题呢?
1.1 为什么会出现webpack
js模块化:
浏览器认识的语言是HTML,CSS,Javascript,而其中css和javascript是通过html的标签link
,script
引入进来。
随着前端项目的越来越复杂,css和js文件会越来越庞大,那么在开发阶段,就必须要把css和js按功能拆分成几个小文件,方便开发。
那么拆分的小文件如何引入到html中呢?css可以通过link
标签或者@import
css语法,但是js因为没有模块导入的语法(ES6有了import,但还不是所有浏览器兼容),就只能通过script
标签引入。但是这样的话会导致很多问题:
- http请求大量增多,影响页面呈现速度。
- 全局变量混乱,难以维护。
针对js模块化出现了很多的解决方案,总结来说有几种规范:
- CommonJs,语法为:require(), module.exports(同步加载,适用于node服务器环境)
- ES6 Mode,语法为:import,export(异步加载,适用于浏览器环境)
- AMD,语法为:require(),define()(异步加载,适用于浏览器环境)
工程化:
除了js模块化的问题之外,前端还有很多其他的问题,比如代码混淆,代码压缩,scss,less等css预编译语言的编译,typescript的编译,eslint检验代码规范,如果这些任务都需要手工去执行的话,太繁琐,也容易出错。
1.2 webpack能做什么
模块化
其实webpack的核心就是解决js模块化问题的工具,运行在node环境中,同时可以支持commonjs,es6,amd的模块语法(可以使用:reuire/mocule.exports,import/export,require/define的方式来导入导出模块)。可以将开发时候拆分为不同文件的js代码,打包成一个js文件。也可以通过配置灵活的拆分js代码,通过 tree shaking 删减没有使用到的代码。
模块化打包时webpack的核心功能,但是它还有两个非常重要的机制loader和plugin。
loader
webpack本身只支持js,json文件的模块化打包,但是有开放出loader接口,通过不同的loader可以将其他格式的文件转化为可识别的模块,比如:
css-loader可以识别css文件,raw-loader可以直接将文件当作模块,less-loader,sass-loader可以直接识别less,sass文件。plugin
插件机制是webpack的另一个重要的拓展,webpack在打包的过程中,会暴露出不同的生命周期事件,而插件会监听这些事件,然后做出对应的操作,比如:
UglifyJsPlugin可以混淆压缩代码,EslintWebpackPlugin可以执行eslint的代码格式检测和自动修复。
总结:
webpack是一个运行在node环境下,对js文件进行模块化打包的工具。通过loader机制可以实现除js格式外的其他格式文件,通过plugin机制可以实现自动执行一些工程化需要的任务。
2 webpack怎么用
那么webpack要怎么使用呢?
2.1 安装运行
首先要安装webapck,使用npm(npm基本用法及原理),安装webpack(核心),webpack-cli(命令行工具):
npm install webpack webpack-cli
然后创建以下两个文件:name.js,index.js,
我们以es6的语法导入导出模块,es6模式的模块变量的导出时按引用导出,就是在模块的变量如果在外部被修改,也会作用到模块内部,而commonjs的模式是按值导出,即模块外部的修改,不会影响到模块内部。
//name.js
let name = "小明"
function say(){
console.log('my name is ',name)
}
export {
name,
say
}
//index.js
import { name,say } from "./name.js";
name = "小红"
say()
console.log('he name is ',name)
然后运行打包命令:
//用npx直接运行webpack命令
npx webpack
//或者用npm的脚本运行打包
//package.json
{
script:{
pack:'webpack'
}
}
npm run pack
webpack默认从index.js文件开始打包,所以如果开始文件的名称为index,就可以不需要写配置文件,就可以直接打包。
默认打包的模式是‘production'即生产模式,打包成功后,会自动创建dist文件夹,并生成main.js文件:
//main.js
(()=>{"use strict";let e="小明";e="小红",console.log("my name is ","小红"),console.log("he name is ","小红")})();
我们看到打包后的文件把index.js和name.js两个文件合成了一个文件,并对代码进行了混淆压缩(生产模式),这是最基本的webpack最核心的功能 6— 打包。
但是显然,在实际工作中我们不会这么简单的使用,那就需要用的配置文件了,下面是一个比较接近实际工作中的例子。
2.2 配置文件 webpack-config.js
以下的项目会有几个文件:index.js , utils.js, style.scss, index.html, webpack-config.js。
基本功能就是在index.js文件中引入utils.js文件里的方法并调用。然后用scss语法编写样式,最后把打包的文件加入到已有的index.html文件中。
通过命令:npx webpack serve(可以放入npm脚本配置中,然后运行 npm run xxx)
,实现的效果是:
- scss自动编译
- index.js utils.js style.scss 文件打包成一个文件
- 把打包的文件自动添加到index.html中
- 打包完成后,自动打开默认浏览器,查看页面
- 文件有变更的话,会自动重新打包,刷新页面
//utils.js
export function sayHello(){
console.log('hello world')
}
//index.js
import './styles.scss'
import { sayHello } from "./utils";
sayHello()
/*styles.scss*/
$bg : black;
$fontC:rgb(218, 17, 117);
body{
background:$bg;
h3{
color:$fontC;
}
}
<!--index.html-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>webpack</title>
<meta name="viewport" content="width=device-width, initial-scale=1"></head>
<body>
<h3>hello webpack</h3>
</body>
</html>
//webpack-config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// 打包模式
mode:'development', //development,production,none
devtool:'cheap-source-map', // eval-source-map,source-map,cheap-source-map
// 入口配置
entry: {
app: './src/index.js',
},
// 出口配置
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,//每次打包,清除dist文件夹
},
// 本地服务器
devServer:{
port:8888,//端口
open:true,//自动打开浏览器
hot:true,//启动热更新
},
// loader
module:{
// 处理scss文件
rules:[
{
test:/\.s[ac]ss$/i,
use:[
'style-loader',//将js模块生成style标签节点
'css-loader',//将css转化成js模块
'sass-loader'//将scss文件编译成css文件
]
}
]
},
// 插件
plugins: [
// 自动把打包后的文件加入到html文件
new HtmlWebpackPlugin({
// 生成html文件的模板
template: './index.html'
}),
],
};
webpack的打包是在node环境下执行的,所以node的语法这里都可以用,最终输出的是一个js对象。
配置可以分成几个部分(参考webpack配置文档):
打包模式
mode 预置了开发环境和生产环境的一些优化。
devtool 控制是否生成,以及如何生成 source map。有了source map文件的话,如果代码有报错可以映射到打包之前的代码(源代码),可以方便定位错误。
可以有很多的选择,一般来说,在开发环境下选择:eval-cheap-module-source-map,cheap-source-map。生产环境选择:不配置,source-map。
入口、出口
- entry 入口文件,webpack会从这个文件开始查找依赖的包,可以配置多个
- output 出口文件,webpack会根据这里的配置,输出打包后的文件。
本地服务器
此功能需要安装webpack-dev-server插件npm install --save-dev webpack-dev-server
,启动时需要用serve命令npx webpack serve
.
启动成功后,会在本地开启一个web服务器,并且有实时更新,热模块替换等功能。
配置项是在devServer
中。loader
webpack本是只支持对js文件的打包,但是因为有loader机制,可以通过配置rules
实现对其他文件的打包。
示例代码中实现的是对scss/sass文件的打包,同一个relues中的loader的执行顺序是从右到左(逆序),所以顺序不能乱。第一个执行的loader 会将其结果(被转换后的资源)传递给下一个 要执行的loader。插件plugins
webpack在打包的时候,会暴露其生命周期,插件就是在特定的生命周期执行的操作,通过插件的机制,可以实现很多强大的功能。
示例代码中使用了HtmlWebpackPlugin
插件,功能是在打包完成后,自动把打包后的代码加入到html文件中。如果没有任何配置,则会自动生成一个html文件,并通过<script>标签把js文件引入进来。
3 实践中的优化
3.1 配置文件拆分与合并--merge
在实际项目中,开发环境和生产环境的配置往往会有很大的区别,所以会有两个配置文件,而这两个配置文件又会有一些公共的配置,所以就会有如下三个配置文件:
- webpack.dev.js
- webpack.prod.js
- webpack.common.js
那么这些配置文件是如何结合的呢?这就要用到webpack-merge插件了。
// webpack.common.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// 入口配置
entry: {
app: './src/index.js',
},
// 出口配置
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,//每次打包,清除dist文件夹
},
// 插件
plugins: [
// 自动把打包后的文件加入到html文件
new HtmlWebpackPlugin({
// 生成html文件的模板
template: './index.html'
}),
],
};
//webpack.dev.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devtool: 'eval-source-map',
// 本地服务器
devServer:{
port:8888,//端口
open:true,//自动打开浏览器
hot:true,//启动热更新
},
});
//webpack.prod.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'production',
devtool:'source-map'
});
// package.json
{
...
"scripts": {
"dev": "webpack serve --config webpack.dev.js",
"build": "webpack --config webpack.prod.js",
},
...
}
然后分别执行npm run dev
,npm run build
就可以了。
3.2 代码分离
webpack会把所有代码打包成一个文件(包括业务代码,npm包),这样最后的包就会很大,打包效率也很慢,所以可以有时候需要做代码分离。
第三方库分离
有一些第三方库可能会需要独立引入,而不是放在业务代码里面,因为不会改动或者需要cdn服务,比如jquery有免费的cdn服务:https://upcdn.b0.upaiyun.com/libs/jquery/jquery-2.0.2.min.js 。
那这些独立引入的js文件就不需要加入到webpack打包,只需要在externals
添加配置就行。// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = {
// 打包模式
mode:'development', //development,production,none
devtool:'cheap-source-map', // eval-source-map,source-map,cheap-source-map
// 入口配置
entry: {
app: './src/index.js',
},
// 出口配置
output: {
filename: '[name].[contenthash].js',//contenthash 是文件内容的hash值
path: path.resolve(__dirname, 'dist'),
clean: true,//每次打包,清除dist文件夹
}, // 插件
plugins: [
// 自动把打包后的文件加入到html文件
new HtmlWebpackPlugin({
// 生成html文件的模板
template: './index.html'
}),
],
};
这样的话,虽然index.js里有引入jquery,webpack也不会把jquery打包进来,打包时间会减少,包的体积也会变小。
npm包分离
第三方库除了一些可以用script标签引入的,大多数是通过npm引入的,这一类的js包也会也会合并到最后的app.js总包之中,使得app.js文件会过大,而且如果业务代码有一点改动的话,app.js的包就会全部都变动,导致浏览器就会重新下载app.js文件,使用不了浏览器内置的缓存机制。
我们可以通过配置,让npm里的包与业务代码分开。// index.js import _ from 'lodash'
import $ from 'jquery'
import { sayHello } from "./utils" $('#title').text('hello jquery')
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = {
// 打包模式
mode:'development', //development,production,none
devtool:'cheap-source-map', // eval-source-map,source-map,cheap-source-map
// 入口配置
entry: {
app: './src/index.js',
},
// 出口配置
output: {
filename: '[name].[contenthash].js', //contenthash是文件内容的hash值
path: path.resolve(__dirname, 'dist'),
clean: true,//每次打包,清除dist文件夹
}, // 插件
plugins: [
// 自动把打包后的文件加入到html文件
new HtmlWebpackPlugin({
// 生成html文件的模板
template: './index.html'
}),
],
optimization: {
runtimeChunk: 'single',// 把webpack引导文件独立出来
splitChunks: {
cacheGroups: {
vendor: {
//所有node_modules下的包合并成一个,并独立出来
test: /[\\/]node_modules[\\/]/,
//控制哪种导入方式的js包才分离出来, 'all'-全部的js包,'async'-异步导入的js包,'initial'-初始导入的js包
chunks: 'all',
name:'vendor' //独立后的包的名称
}
}
}
},
};这样打包目录下就有三个文件:
- app.js: 业务代码
- runtime.js:webpack的引导代码
- vendor.js: npm引入的js包代码
一般有变动的就只有app.js文件了。npm引入的js包是否可以再分成几个文件呢?可以的,参看 SplitChunksPlugin文档
业务代码分离
有时候不仅第三方库需要分离,我们自己写的业务代码可能也会很大,也需要分离。要实现业务代码的分离只要添加多个入口就可以了。// index.js
import { sayHello } from "./utils" sayHello()
console.log('hello index')
// utils.js
export function sayHello(){
console.log('hello utils')
}
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = {
mode: 'development',
devtool: 'cheap-source-map',
// 入口配置
entry: {
app: {
import:'./src/index.js',
dependOn:'utils'
},
utils:'./src/utils.js'
},
// 出口配置
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true,//每次打包,清楚dist文件夹
}, // 插件
plugins: [
// 自动把打包后的文件加入到html文件
new HtmlWebpackPlugin({
// 生成html文件的模板
template: './index.html'
}),
],
};
这样utils.js文件也从主包app.js中分离了出来,要注意的是app入口加了
dependOn:'utils'
,为了让app.js里面不要重复打包utils.js。动态加载
有时候不是需要页面一开始的时候,就加载全部的js包,而是等到特定的时机再去加载某些js包,这就需要动态加载了,只需要用到import()
就可以了,注意这是import的函数使用方式。// index.js const element = document.createElement('div');
element.id = 'title'
element.innerHTML ='Hello webpack'; const button = document.createElement('button');
button.innerHTML = 'Click me';
button.onclick = importJquery; document.body.appendChild( element);
document.body.appendChild( button); async function importJquery(){
const { default: $ } = await import('jquery');
$('#title').text('hello jquery')
}
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = {
mode: 'development',
devtool: 'cheap-source-map',
// 入口配置
entry: {
app: {
import:'./src/index.js',
},
},
// 出口配置
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true,//每次打包,清楚dist文件夹
}, // 插件
plugins: [
// 自动把打包后的文件加入到html文件
new HtmlWebpackPlugin({
// 生成html文件的模板
template: './index.html'
}),
],
};
如果运行代码的话,会发现页面一开始进入的时候,并没有引入jquery的包,但是点击按钮的时候,就开始导入了,这就动态加载。
并且发现webpack.config.js并没有做什么特殊的配置,这是因为动态导入的js包webpack会自动给独立为一个js文件。
import()返回的是一个promise对象。
3.3 动态链接库 dll
webpack每次打包的时候,都会把涉及到的js包都处理一遍。但是实际上有些js包是不会有改动到的,所以打包过后的文件每次都是一样的,每次都重新打包的话,会加增打包时间。
有一种解决方案是:把不会变动的js包先打包一次,以后每次打包的时候,直接引用就可以了。
先添加一个独立的配置文件 webpack.dll.config.js
//webpack.dll.config.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
mode: 'production',
// 入口文件
entry: {
// 项目中用到该两个依赖库文件
jquery_lodash: ['jquery','lodash'],
},
// 输出文件
output: {
// 文件名称
filename: '[name].dll.js',
// 将输出的文件放到dll目录下
path: path.resolve(__dirname, 'dll'),
// 文件输出的全局变量
library: '_dll_[name]',
},
plugins: [
// 使用插件 DllPlugin
new webpack.DllPlugin({
// 动态链接库的全局变量名称,需要和 output.library 中保持一致
// 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
name: '_dll_[name]',
// 描述动态链接库的 manifest.json 文件输出时的文件名称
path: path.join(__dirname, 'dll', '[name].manifest.json')
}),
]
};
执行打包脚本 npx webpack --config webpack.dll.config.js
,就会再dll目录下输出文件:jquery_lodash.dll.js
,jquery_lodash.manifest.json
。
主要用到的插件是:
DllPlugin
的作用就是生成manifest.json文件。
然后配置项目打包用的webpack.config.js文件:
//webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
module.exports = {
mode: 'development',
devtool: 'cheap-source-map',
// 入口配置
entry: {
app: {
import:'./src/index.js',
},
},
// 出口配置
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true,//每次打包,清楚dist文件夹
},
// 插件
plugins: [
// 自动把打包后的文件加入到html文件
new HtmlWebpackPlugin({
// 生成html文件的模板
template: './index.html'
}),
// 引用dll中的文件,
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./dll/jquery_lodash_dll.manifest.json')
}),
//把dll文件加入到index.html中
new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, './dll/jquery_lodash_dll.dll.js'),
publicPath: './',
}),
],
};
主要用到的插件是:
DllReferencePlugin
检索引用文件的时候,如果发现manifest.json里面有,就告诉webpack不要打包该文件,因为已经打包好了。AddAssetHtmlWebpackPlugin
因为webpack没有打包dll里的文件,所以需要手动把它加入到index.html中。
然后就可以正常的打包项目了:
// index.js
import _ from 'lodash'
import $ from 'jquery'
$('#title').text('hello jquery')
执行打包脚本 npx webpack
,会自动使用webpack.config.js配置文件打包。会发现打包速度提高了很多。
webpack基本用法及原理(10000+)的更多相关文章
- npm基本用法及原理(10000+)
作为前端开发者,应该每个人都用过npm,那么npm到底是什么东西呢?npm run,npm install的时候发生了哪些事情呢?下面做详细说明. 1.npm是什么 npm是JavaScript ...
- imadjust从用法到原理—Matlab灰度变换函数之一
imadjust从用法到原理-Matlab灰度变换函数之一 转摘网址:http://blog.sina.com.cn/s/blog_14d1511ee0102ww6s.html imadjust函数是 ...
- Vue(基础七)_webpack(webpack异步加载原理)
---恢复内容开始--- 一.前言 1.webpack异步加载原理’ 2.webpack.ensure原理 ...
- Python 中 -m 的典型用法、原理解析与发展演变
在命令行中使用 Python 时,它可以接收大约 20 个选项(option),语法格式如下: python [-bBdEhiIOqsSuvVWx?] [-c command | -m module- ...
- synchronized是什么,用法及原理
文章转Hollis博客 大家可以关注下,很多技术类型的文章 在再有人问你Java内存模型是什么,就把这篇文章发给他.中我们曾经介绍过,Java语言为了解决并发编程中存在的原子性.可见性和有序性问题,提 ...
- Java之反射 — 用法及原理
Java之反射 - 用法及原理 定义 Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意方法和属性:这种动态获取信息以及动态调用对象 ...
- java this的用法以及原理
/** * this存在方法中,在方法中被调用. * 且是非static方法中被调用.(this 表示这个类的当前实例,而静态方法不依赖于该类的任何实例,随着类产生而装载,因此方法内不能引用 this ...
- webpack进阶用法你都get到了么?
如何消除无用代码:打包自己的私有js库:实现代码分割和动态import提升初次加载速度:配置eslint规范团队代码规范:打包异常抓捕你都get到了么? 摇树优化:Tree Shaking webpa ...
- ASP.NET Core MVC 模型绑定用法及原理
前言 查询了一下关于 MVC 中的模型绑定,大部分都是关于如何使用的,以及模型绑定过程中的一些用法和概念,很少有关于模型绑定的内部机制实现的文章,本文就来讲解一下在 ASP.NET Core MVC ...
随机推荐
- 证明:(a,[b,c]) = [(a,b),(a,c)]
这题是潘承洞.潘承彪所著<初等数论>(第三版)第一章第5节里一个例题,书中采用算术基本定理证明,并指出要直接用第4节的方法来证是较困难的. 现采用第4节的方法(即最大公约数理论里的几个常用 ...
- PENETRATION第一步
PENETRATION第一步 第一次去打靶机,本来都快成功了,电脑蓝屏警告了...(=_=) 靶机下载连接 (https://download.vulnhub.com/admx/AdmX_new.7z ...
- Linux服务器JDK的安装
JDK安装 开发java程序必须要的环境 下载JDK rpm. 安装环境 #检测当前系统是否安装Java环境 java -version #如果有就需要卸载 #rpm -qa|grep jdk #检测 ...
- 新版idea无法导入mavenweb模板
目前没有任何办法,最好是下个旧版的
- vue 根据身份证计算出出生日期和判断性别
//获取生日和性别 getBirth(idCard) { var birthday = ""; if(idCard != null & ...
- JVM(一)类加载器与类加载过程
JVM是面试必面的一个知识点,也是高级程序员必备的一个技能.以下是JVM整体核心内容,包括类加载系统,运行时数据区内部结构,执行引擎,本地方法接口. 首先来学习类的加载器,虚拟机把描述类的数据从Cla ...
- C++11多线程编程
1. 多线程编程 在进行桌面应用程序开发的时候, 假设应用程序在某些情况下需要处理比较复杂的逻辑, 如果只有一个线程去处理,就会导致窗口卡顿,无法处理用户的相关操作.这种情况下就需要使用多线程,其中一 ...
- Qt 6.0精简WebEngine SerialPort Multimedia等成为半残GUI框架一览
由于 Qt 集成了大量成熟模块,使之成为 C++ 领域中最好用的开源技术跨平台 GUI 开发框架.基于 Qt 能开发 Windows MacOS 传统桌面或无 GUI 应用程序.Unix/Linux ...
- 全局CSS样式表
看api手册使用即可 1.按钮和图片 2.表格.表单 表单的lable作用就是点击前面的文字可以聚焦到对应的输入框中
- 【第二篇】- Maven 环境配置之Spring Cloud直播商城 b2b2c电子商务技术总结
Maven 环境配置 Maven 是一个基于 Java 的工具,所以要做的第一件事情就是安装 JDK. 如果你还未安装 JDK,可以参考我们的 Java 开发环境配置. 系统要求 项目 要求 JDK ...