本文目录:
webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
webpack有四个核心概念:
入口(entry),输出(output),loader,插件(plugins)
webpack的入口文件模板结构:
module.exports = {
//入口配置
entry: '',
//出口配置
output: '',
//模块配置
module: {
rules: [
{
test: /\.css/,
use: ["style-loader", "css-loader"]
}
]
},
//插件配置
plugins: {},
//模式配置,开发模式还是生产模式
mode:'',
//开发服务器配置
devServer: {},
//解析配置
resolve: {}
}
webpack.config.js导出一个Object对象(或者导出一个Function,或者导出一个Promise函数,还可以导出一个数组包含多份配置)。Webpack从入口文件开始,识别出源码中的模块化导入语句,递归地找出所有依赖,然后把入口文件和所有依赖打包到一个单独的文件中(即一个chunk),这就是所谓的模块打包。
webpack运行的基本流程分为初始化、编译、输出三个阶段.
初始化:
从配置文件和shell文件读取、合并参数;
加载plugin
实例化compiler
编译:
从entry发出,针对每个module串行调用对应loader编译文件内容
找到module依赖的module,递归进行编译处理
输出:
把编译后module组合成chunk
把chunk转换成文件,输出到文件系统
Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;
确定入口:根据配置中的 entry 找出所有的入口文件;
编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果
在代码中所有被import()的模块,都将打成一个单独的包,放在chunk存储的目录下。在浏览器运行到这一行代码时,就会自动请求这个资源,实现异步加载。
ES6的import语法告诉我们,模块只能做静态加载。
所谓静态加载,就是你不能写成如下形式:
let filename = 'module.js';
import {mod} from './' + filename.
//也不能写成如下形式:
if(condition) {
import {mod} from './path1'
} else {
import {mod} from './path2'
}
先webpack4以后的版本的支持下,import可以进行动态加载,大致用法如下:import()接收一个路径参数,然后通过then的方式引入模块
let filename = 'module.js';
import('./' + filename). then(module =>{
console(module);
}).catch(err => {
console(err.message);
});
//如果你知道 export的函数名
import('./' + filename). then(({fnName}) =>{
console(fnName);
}).catch(err => {
console(err.message);
});
这里有一点要注意的是:
import的加载是加载的模块的引用。而import()加载的是模块的拷贝,就是类似于require(),怎么来说明?看下面的例子:
module.js 文件:
export let counter = 3;
export function incCounter() {
counter++;
}
main.js 文件:
let filename = 'module.js';
import('./' + filename).then(({counter, incCounter})=>{
console.log(counter); //3
incCounter();
console.log(counter); //3
});
原本的import写法:
import {counter, incCounter} from './module.js';
console.log(counter); //3
incCounter();
console.log(counter); //4
loader是 webpack 用于在编译过程中解析各类文件格式,并输出;
loader(加载器)是一个代码转换器,它由 webpack 的 loader runner 执行调用,接收原始资源数据作为参数(当多个加载器联合使用时,上一个loader的结果会传入下一个loader),最终输出 javascript 代码(和可选的 source map)给 webpack 做进一步编译。
手写loader的思路:
loader本质上就是一个 node 模块,通过写一个函数来完成自动化的过程。
这里通过写一个最简单的loader来理解手写loader的思路。
当只有一个 loader 应用于资源文件时,它接收源码作为参数,输出转换后的 js 代码。文件路径:loaders/simple-loader.js
module.exports = function loader (source) {
console.log('simple-loader is working');
return source;
}
这就是一个最简单的 loader 了,这个 loader 啥也没干,就是接收源码,然后原样返回,为了证明这个loader被调用了,我在里面打印了一句话‘simple-loader is working’。
测试这个 loader:
若是使用 npm 安装的第三方 loader,直接写 loader 的名字就可以了。但是现在用的是自己开发的本地 loader,需要我们手动配置路径,告诉 webpack 这些 loader 在哪里。
// webpack.config.js
const path = require('path');
module.exports = {
entry: {...},
output: {...},
module: {
rules: [
{
test: /\.js$/,
// 直接指明 loader 的绝对路径
use: path.resolve(__dirname, 'loaders/simple-loader')
}
]
}
}
执行webpack编译,可以看到,控制台输出 ‘simple-loader is working’。说明 loader 成功被调用。
wenpack根据自己的工作机制提供了许多hooks,类似于Vue的生命周期。
例如:run(开始编译阶段),make( 从 entry 开始递归分析依赖,准备对每个模块进行 build),done(完成所有的编译过程)
plugin必须是一个函数,或者是一个包含apply的对象。一般来说我们都会定义一个类型,然后在这个类型中定义apply方法,最后再通过这个类型来创建一个实例对象去使用这个插件。
例如下面这段代码
const pluginName = 'myplugin'
module.exports = class myplugin {
apply(){}
}
这个apply方法接收一个叫compiler的参数对象,这个对象是webpack工作中最核心的对象,包含了此次打包构建的所有配置信息,我们就可以通过这个对象去注册钩子函数。
const pluginName = 'myplugin'
module.exports = class myplugin {
apply(compiler){
compiler.hooks.run.tap(pluginName, () =>{
{
console.log('开始编译');
}
})
}
}
我们想在run阶段输出‘开始编译’这句话,在webpack.config.js中引入并配置
const myplugin = require('./myplugin')
...
plugins:[
new myplugin()
]
...
进行webpack编译,在控制台可以看到在开始阶段输出了内容,说明plugin生效了。
对于loader,它是一个转换器,将A文件进行编译形成B文件,这里操作的是文件,比如将A.scss转换为A.css,单纯的文件转换过程。
plugin是一个扩展器,它丰富了webpack本身,针对是loader结束后,webpack打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听webpack打包过程中的某些节点,执行广泛的任务。
不同的作用
不同的用法
Tree shaking 是一种通过清除多余代码方式来优化项目打包体积的技术。
我们在项目中创建一个utils.js文件:
export function add(a, b) {
console.log('add');
return a + b;
}
export function minus(a, b) {
console.log('minus');
return a - b;
}
export function multiply(a, b) {
console.log('multiply');
return a * b;
}
export function divide(a, b) {
console.log('divide');
return a / b;
}
index.js文件中导入utils.js的add方法并调用:
import { add } from './utils';
add(10, 2);
运行npm run build后查看dist/bundle.js文件,可以发现utils.js中所有的代码都打包了,并没有像我们预期的那样只打包add()函数。
CommonJS的动态特性模块意味着tree shaking不适用。因为它是不可能确定哪些模块实际运行之前是需要的或者是不需要的。在ES6中,进入了完全静态的导入语法:import。ES6的import语法完美可以使用tree shaking,因为可以在代码不运行的情况下就能分析出不需要的代码。
webpack4以后的版本,只需要将mode设置为production即可开启tree shaking。
模块热替换(HMR - Hot Module Replacement)允许在运行时替换,添加,删除各种模块,而无需进行完全刷新重新加载整个页面。
一个带有热替换功能的webpack.config.js 文件的配置如下,做了这么几件事
1.通过
嵌套tree-shaking的实现
移除Node.js polyfills 自动加载功能
有效减少打包后的文件体积。
2.生成的代码不再仅仅是ES5,也会生成 ES6 的代码
3.optimization配置中优化了minSize&maxSize的配置方式,对js和css有了区分,单位是kb
optimization: {
runtimeChunks: {},
splitChunks: {},
// 在文件大小为0-30kb的情况下进行文件分割
minSize: {
javaScript: 0,
style: 0
},
maxSize: {
javaScript: 30,
style: 30
}
}
4.在配置文件中使用cache: {type: "filesystem"}配置实现持久化缓存,提高构建速度
优化可以从两个方面考虑,一个是优化开发体验,一个是优化输出质量。
①缩小文件搜索范围
resolve字段告诉webpack怎么去搜索文件,所以首先要重视resolve字段的配置:
由于loader对文件转换操作很耗时,应该尽量减少loader处理的文件,可以使用include命中需要处理的文件,缩小命中范围。
②DllPlugin可以将特定的类库提前打包然后引入
DllPlugin是webpack的内置插件,这种方式可以极大的减少打包类库的次数,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独文件的优化方案
③HappyPack
因为Node是单线程运行的,所以Webpack在打包的过程中也是单线程的,特别是在执行Loader的时候,这样会导致等待的情况,HappyPack可以将Loader的同步执行转换为并行的,HappyPack插件需要另外安装。
④使用source-map优化代码调试
在webpack.config.js中加入devtool:'source-map'可以让构建后代码出错,会通过映射关系追踪源代码错误。
实际开发中我们往往只需要在开发环境中开启source-map
const isProd = process.env.NODE_ENV === 'production';
module.exports = {
devtool: isProd
? false
: '#cheap-module-source-map',
}
⑤热更新HMR
利用webpack内置插件HotModuleReplacementPlugin,无需在每次更改内容时都重新加载整个页面。
优化输出质量最大的好处就是可以减少首屏的加载时间
①按需加载路由
如果我们把十几个页面甚至更多的路由页面,把这些页面全部打包进一个JS文件的话,虽然将多个请求合并了,但是同样也加载了很多并不需要的代码,耗费了更长的时间。那么为了首页能更快地呈现给客户,这时候我们就可以使用按需加载,将每个路由页面单独打包为一个文件
②使用Tree Shaking,删除项目中未被引用的代码。
③开启Scope Hoisting
Scope Hoisting直译就是作用域提升,Scope Hoisting会分析出模块之间的依赖关系,尽可能的把打包出来的模块合并到一个函数中,让Webpack打包出来的代码更小、运行更快。
Scope Hoisting 是webpack内置的功能,只要配置一个插件即可
module.exports = {
plugins: [
// 开启 Scope Hoisting 功能
new webpack.optimize.ModuleConcatenationPlugin()
]
}
④区分环境--减小生产环境代码体积
代码运行环境分为开发环境和生产环境,代码需要根据不同环境做不同的操作,许多第三方库中也有大量的根据开发环境判断的if else代码,构建也需要根据不同环境输出不同的代码,所以需要一套机制可以在源码中区分环境,区分环境之后可以使输出的生产环境的代码体积减小。Webpack中使用内置DefinePlugin插件来定义配置文件适用的环境。
plugins:[
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production')
}
})
]
注意,JSON.stringify('production') 的原因是,环境变量值需要一个双引号包裹的字符串,而stringify后的值是'"production"'
然后就可以在源码中使用定义的环境:
if(process.env.NODE_ENV === 'production'){
console.log('你在生产环境')
doSth();
}else{
console.log('你在开发环境')
doSthElse();
}
⑤使用terser-webpack-plugin插件压缩JS代码
如果使用的是 webpack v5 或以上版本,你不需要安装这个插件。webpack v5 自带最新的 terser-webpack-plugin。如果使用 webpack v4,则必须安装 terser-webpack-plugin v4 的版本。
npm install terser-webpack-plugin --save-dev
然后将插件添加到你的 webpack 配置文件中
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [new TerserPlugin()],
},
};
⑥压缩图片资源
对于某些网站,图像占据了页面很大部分,虽然它们不会阻塞页面渲染,但是它们仍然占用了很大一部分带宽,在webpack中可以使用url-loader来优化。
url-loader 可以将小型静态文件内联到应用程序中。如果不进行配置,它将把接受一个传递的文件,将其放在已编译的包旁边,并返回该文件的url。但是,如果指定 limit 选项,它将把小于这个限制的文件编码为Base64 数据的 url 并返回这个url,这会将图像内联到 JavaScript 代码中,从而可以减少一个HTTP请求。
module.exports = {
module: {
rules: [
{
test: /\.(jpe?g|png|gif)$/,
loader: 'url-loader',
options: {
// Inline files smaller than 10 kB (10240 bytes)
limit: 10 * 1024,
},
},
],
}
};
三者都是前端构建工具,grunt和gulp在早期比较流行,现在webpack相对来说比较主流,不过一些轻量化的任务还是会用gulp来处理,比如单独打包CSS文件等。
grunt和gulp是基于任务和流(Task、Stream)的。类似jQuery,找到一个(或一类)文件,对其做一系列链式操作,更新流上的数据, 整条链式操作构成了一个任务,多个任务就构成了整个web的构建流程。
webpack是基于入口的。webpack会自动地递归解析入口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin来扩展webpack功能。
所以总结一下:
从构建思路来说
gulp和grunt需要开发者将整个前端构建过程拆分成多个Task,并合理控制所有Task的调用关系 webpack需要开发者找到入口,并需要清楚对于不同的资源应该使用什么Loader做何种解析和加工复制代码
对于知识背景来说
gulp更像后端开发者的思路,需要对于整个流程了如指掌 webpack更倾向于前端开发者的思路
Npm是目前最大的 JavaScript 模块仓库,里面有来自全世界开发者上传的可复用模块。你可能只是JS模块的使用者,但是有些情况你也会去选择上传自己开发的模块。 关于NPM模块上传的方法可以去官网上进行学习,这里只讲解如何利用webpack来构建。
NPM模块需要注意以下问题:
.css文件也需要包含在发布的模块里。基于以上需要注意的问题,我们可以对于webpack配置做以下扩展和优化:
1.CommonJS模块化规范的解决方案: 设置output.libraryTarget='commonjs2'使输出的代码符合CommonJS2 模块化规范,以供给其它模块导入使用
输出ES5代码的解决方案:使用babel-loader把 ES6 代码转换成 2.ES5 的代码。再通过开启devtool: 'source-map'输出SourceMap以发布调试。
3.Npm包大小尽量小的解决方案:Babel 在把 ES6 代码转换成 ES5 代码时会注入一些辅助函数,最终导致每个输出的文件中都包含这段辅助函数的代码,造成了代码的冗余。解决方法是修改.babelrc文件,为其加入transform-runtime插件
4.不能将依赖模块打包到NPM模块中的解决方案:使用externals配置项来告诉webpack哪些模块不需要打包。
5.对于依赖的资源文件打包的解决方案:通过css-loader和extract-text-webpack-plugin来实现,配置如下:
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
module: {
rules: [
{
// 增加对 CSS 文件的支持
test: /\.css/,
// 提取出 Chunk 中的 CSS 代码到单独的文件中
use: ExtractTextPlugin.extract({
use: ['css-loader']
}),
},
]
},
plugins: [
new ExtractTextPlugin({
// 输出的 CSS 文件名称
filename: 'index.css',
}),
],
};
按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭9年前。我最近开始学习Ruby,这是我的第一门编程语言。我对语法感到满意,并且我已经完成了许多只教授相同基础知识的教程。我已经写了一些小程序(包括我自己的数组排序方法,在有人告诉我谷歌“冒泡排序”之前我认为它非常聪明),但我觉得我需要尝试更大更难的东西来理解更多关于Ruby.关于如何执行此操作的任何想法?
在我的mac上安装几个东西时遇到这个问题,我认为这个问题来自将我的豹子升级到雪豹。我认为这个问题也与macports有关。/usr/local/lib/libz.1.dylib,filewasbuiltfori386whichisnotthearchitecturebeinglinked(x86_64)有什么想法吗?更新更具体地说,这发生在安装nokogirigem时日志看起来像:xslt_stylesheet.c:127:warning:passingargument1of‘Nokogiri_wrap_xml_document’withdifferentwidthduetoproto
在Rails3.x应用程序中,我正在使用net::ssh并向远程pc运行一些命令。我想向用户的浏览器显示实时日志。比如,如果两个命令在net中运行::ssh执行即echo"Hello",echo"Bye"被传递然后"Hello"应该在执行后立即显示在浏览器中。这是代码我在rubyonrails应用程序中使用ssh连接和运行命令Net::SSH.start(@servers['local'],@machine_name,:password=>@machine_pwd,:timeout=>30)do|ssh|ssh.open_channeldo|channel|channel.requ
我正在为Jekyll编写一个转换器插件,需要访问一些页眉(YAML前端)属性。只有内容被传递给主要的转换器方法,似乎无法访问上下文。例子:moduleJekyllclassUpcaseConverter关于如何在转换器插件中访问页眉数据有什么想法吗? 最佳答案 基于Jekyll源代码,无法在转换器中检索YAML前端内容。根据您的情况,我看到了两种可行的解决方案。您的文件扩展名可以具有足够的描述性,以提供您本应包含在前言中的信息。看起来Converter插件的设计就是这么基本的。如果修改Jekyll是一个选项,您可以更改Convert
华为认证分等级的,相当于初中高三个等级,当然高级是比较难考的,也是含金量最高的。我就慢慢给你介绍一下。1.了解华为认证华为认证网络工程师是由华为公司认证与采购部推出的独立认证体系,与之前的华为认证不同,简称HCIA。同时华为认证是华为技术有限公司凭借多年信息通信技术人才培养经验,以及对行业发展的理解,以层次化的职业技术认证为指引,推出的覆盖IP、IT、CT以及ICT融合技术领域的认证体系,是ICT全技术领域认证体系。2.怎么考取华为认证网络工程师?要考取华为认证网络工程师必须选择最近的Prometric授权考试中心APTC报名并参加GB0-190的考试,考试通过后,以获得由华为统一签发的“华
大家好,我叫胡飞虎,花名虎仔,目前负责云效旗下产品Codeup代码托管的设计与开发。代码作为企业最核心的数据资产,除了被构建、部署之外还有更大的价值。为了帮助企业和团队挖掘更多源代码价值以赋能日常代码研发、运维等工作,云效代码团队在大数据和智能化方向进行了一系列的探索和实践(例如代码搜索与推荐),本文主要介绍我们如何通过直接打通源代码来提高研发与运维效率。随着微服务架构的流行,一个业务流程需要多个微服务共同完成。一旦出现问题,运维人员在面对数量多、调用链路复杂的情况下,很难快速锁定导致问题发生的罪魁祸首:代码。为了提高排查效率,目前常见的解决方案是:链路跟踪+日志分析工具相结合。即通过链路跟踪
一、简介之前在Vue项目中使用过element的上传组件,实现了点击上传+拖拽上传的两种上传功能。然后我就在想是否可以通过原生的html+js来实现文件的点击上传和拖拽上传,说干就干。首先是点击获取上传文件自然没的说,只需要借助input标签即可,但原生的点击上传按钮,实在是过于简陋,所以我的想法是通过一个div,模拟成上传按钮,然后监听其点击事件,通过input.click()去模拟点击真正的上传元素。然后是拖拽获取上传文件,这个稍有难度,我的想法是通过HTML5新增的drag拖放API+dataTransfer来实现文件的拖拽获取,但是由于是html5新增的,所以可能在某些低版本IE浏览器
Asitcurrentlystands,thisquestionisnotagoodfitforourQ&Aformat.Weexpectanswerstobesupportedbyfacts,references,orexpertise,butthisquestionwilllikelysolicitdebate,arguments,polling,orextendeddiscussion.Ifyoufeelthatthisquestioncanbeimprovedandpossiblyreopened,visitthehelpcenter提供指导。已关闭8年。什么是学习ruby语言
项目背景和意义 目的:本课题主要目标是设计并能够实现一个基于微信校园跑腿小程序系统,前台用户使用小程序发布跑腿任何和接跑腿任务,后台管理使用基于PHP+MySql的B/S架构;通过后台管理跑腿的用户、查看跑腿信息和对应订单。意义:手机网络时代,大学生通过手机网购日常用品、外卖外卖、代取快递等已不再是稀奇的事情。此外,不少高校还流行着校园有偿工作,校园跑腿就成了大学生创业服务项目。 因为你在校园里,所以不会有进入的限制。并不是所有的外卖平台都可以随意进入校园,比如小黄和小蓝的双打外卖平台。许多大学禁止送餐进入学校,更不用说送餐进入宿舍了。这一措施使得校园服务市场的竞争相对不
一、介绍一下vercelvercel是一个站点托管平台,提供CDN加速,同类的平台有Netlify和GithubPages,相比之下,vercel国内的访问速度更快,并且提供Production环境和development环境,对于项目开发非常的有用的,并且支持持续集成,一次push或者一次PR会自动化构建发布,发布在development环境,都会生成不一样的链接可供预览。但是vercel只是针对个人用户免费,teams是收费的首先vercel零配置部署,第二访问速度比github-page好很多,并且构建很快,还是免费使用的,对于部署个人前端项目路、接口服务非常方便vercel类似于git