如今,前端开发领域的变化用日新月异来形容,丝毫不过分,当然也可以换言之:“别更新了,臣妾真心学不动了~”
早在前端库和框架出现之前,“小伙伴们的生活”是如此朴实无华,过去......就只有一个简单的javascript file,而你不得不把它包含到html文件中,然后呢......完事儿!
在CSS中,多了些库和框架可以使用;JavaScript生态系统也是同样......我们已迎来了ES2020,甚至更高版本的标准,你懂的;过去几年,Angular、React、Vue三大框架也竞相角逐。
然后就是所谓的“module bundlers”,捆绑器或构建工具,包括webpack、browserify和gulp等等,进入主题!你是否想过我们的工具是如何变得越来越复杂哒?!以至于不得不使用诸如webpack之类的构建工具?
1 . 回看历史
1-1 混沌之初的万物
在库、框架和Webpack等构建工具不曾存在的那个“年代”,我们只需要在HTML中包含一个脚本即可,问题立马解决~ 如下:
<head>
<script src =“ main.js” type =“ text / javascript”> </ script>
</ head>
后来有了库,这样我们不得不一个一个地以适当的顺序将它们纳入,使它们相互依赖进行工作:
<head>
<script src="library1.js" type="text/javascript"></script>
<script src="library2.js" type="text/javascript"></script>
<script src="script.js" type="text/javascript"></script>
</head>
再有后来......整个生态系统蓄势待发~添加了诸如Ember、Backbone、Meteor、Angular、React......Vue之类的框架,使事情变得有些复杂了,进而添加一些不断推陈出新的JS标准,例如ES6、ES7及更高版本。
那么才到了构建工具上,如browserify、grunt、WebPack......甚至EsBuild,这样我们才混到了今天的一脸懵逼和怅然若失境界......
var webpack = require("webpack");
var path = require("path");
var HtmlWebpackPlugin = require("html-webpack-plugin");
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
entry: {
bundle: './src/index.js'
},
output: {
path: path.join(__dirname, "../dist"),
//note: we changed `bundle` name into a variable `[name]` to get the key values in `entry` property instead of declaring the name statically.
//[chunkhash] - this is a large string of characters that uses hash. If vendor or javascript files were updated, webpack will automatically bundle the contents of the file then generate a different hash.
filename: "[name].[chunkhash].js"
},
mode: "development",
devtool: "inline-source-map",
devServer: {
proxy: {
'/api': {
target: "http://localhost:3000",
secure: false,
changeOrigin: true
}
}
},
module: {
rules: [
{
use: {
loader: "babel-loader"
},
test: /\.js$/,
exclude: /node_modules/ //excludes node_modules folder from being transpiled by babel. We do this because it's a waste of resources to do so.
},
{
use: ['style-loader', 'css-loader'],
test: /\.css$/
}
]
},
plugins: [
//`manifest` - Gives the browser a better understanding that tells whether the vendor file has actually got changed.
// new webpack.optimize.CommonsChunkPlugin({
// names: ['vendor', 'manifest']
// }), //We need to include this plugin so that it never duplicates the libraries that were included in `vendor.js` within `bundle.js` as well
new HtmlWebpackPlugin({
template: 'src/index.html'
}), //this plugin is responsible for injecting the entry scripts of webpack (such as `bundle.js` and `vendor.js`) inside the html file without specifying them manually.
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) //we will set the correct variable for `process.env.NODE_ENV` variable inside the `scripts` property in `package.json`
}), //This adds windows-scoped variables that will be defined in bundle.js
// new BundleAnalyzerPlugin()
],
optimization: {
splitChunks: {
chunks: 'async',
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
var webpack = require("webpack");
var path = require("path");
var HtmlWebpackPlugin = require("html-webpack-plugin");
const CompressionPlugin = require("compression-webpack-plugin");
module.exports = {
entry: {
bundle: './src/index.js'
},
output: {
path: path.join(__dirname, "../dist"),
//note: we changed `bundle` name into a variable `[name]` to get the key values in `entry` property instead of declaring the name statically.
//[chunkhash] - this is a large string of characters that uses hash. If vendor or javascript files were updated, webpack will automatically bundle the contents of the file then generate a different hash.
filename: "[name].[chunkhash].js"
},
mode: "production",
module: {
rules: [
{
use: {
loader: "babel-loader"
},
test: /\.js$/,
exclude: /node_modules/ //excludes node_modules folder from being transpiled by babel. We do this because it's a waste of resources to do so.
},
{
use: ['style-loader', 'css-loader'],
test: /\.css$/
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}), //this plugin is responsible for injecting the entry scripts of webpack (such as `bundle.js` and `vendor.js`) inside the html file without specifying them manually.
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production') //we will set the correct variable for `process.env.NODE_ENV` variable inside the `scripts` property in `package.json`
}) //This adds windows-scoped variables that will be defined in bundle.js
],
optimization: {
minimize: true,
splitChunks: {
chunks: 'async',
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
必须承认,当年我及小伙伴们开始了解、阅读这些堆栈时,也是略显胆怯,直到稍微缓缓神,
注意,未完......
2 . 什么是Webpack
首先,根据官方网站,Webpack是用于现代JavaScript应用的静态模块捆绑器,它构建了一个依赖图(dependency graph );无论JavaScript文件相互依赖时,它们各自位于何处,这个图用于将javascript模块捆绑为一体。
简而言之,Webpack是一个捆绑器,负责捆绑所有JavaScript文件以促成它们正常工作。
2-1 Webpack救民于水火
从前面的1-1,你知道了我们是怎么样成功“作茧自缚”的,那么Webpack像超人一样来解救我们了!构建webpack之类的工具可以解决哪些问题?
如下是Webpack试图解决的问题:
2-1-1 自动化
你或许不太喜欢把每个javascript库加入到HTML的headers中,但却喜欢使用npm把要在功能中使用到的这些库纳入进去。
Webpack能够做到这一点,我们只需要从npm安装所需选入的任何库,然后webpack会自动在bundle中包含该库(前提是的确在模块中会使用它们)。
2-1-2 加载速度
如果我们大力创建一个现代的Web应用,那么在网页中加载单个脚本的成本非常高。
仍旧是Webpack!它通过把我们拥有的每个javascript模块捆绑在一起,从而为我们提高加载速度,这是因为从webserver获取脚本时只请求一次;其原理是——与webserver去请求一个大文件相比,一个接一个地获取javascript文件会给Web服务器施加更多压力。
2-1-3 按需加载(必要脚本)
通常情况下,你开发的应用都会去加载所拥有的每个javascript模块和库,不管这些功能是否需要;但是万一在某些条件下,特定功能或模块中根本不需要某些库?
查看控制台中以下图表:
上图中红色表示我们应用中未使用脚本的百分比情况,绿色的表示正在使用该脚本。那么思考一下......假设我们有更多未被使用的脚本,这些脚本在加载期间会不必要地加载,这应该不是你希望看到的~
webpack提供了一种称为“代码拆分”的功能帮助我们解决该问题;它的原理是,我们实际上可以选择仅在应用需要时才“按需”或“异步”加载单个脚本,从而通过以极快的速度来加载,为我们的应用提供了相对的性能优势。
由于“代码拆分”,我们不再需要加载不必要的脚本,这些脚本只会在用户不使用某些特定模块或功能时妨碍性能。
有关代码拆分的更多信息,不赘述,可访问webpack的官方文档:
相关链接:https://webpack.js.org/guides/code-splitting/
2-1-4 依赖问题
Webpack受欢迎的另一个原因是,它解决了构建javascript应用时的常见问题:依赖性。
如前所述,在webpack出现之前,我们通常以正确的顺序排列脚本和库以正确连接依赖项:
<script src="moment.min.js"></script>
<script src="typeahead.min.js"></script>
<script src="jquery.min.js"></script>
<script src="otherplugins.min.js"></script>
<script src="main.js"></script>
虽然不能说不可以,但想一想,随着你所依赖的库列表的增多,会遇到一些依赖的问题,这是因为没有以正确的顺序导入脚本。当所依赖的库列表越来越广时,会遇到很多类似问题。
但是当我们开始使用webpack时,这迎刃而解!因为你会通过使用ECMAScript模块(ESM)提前知道在构建期间缺少哪些依赖关系,如下所示:
//helper.js
export const sqrt = Math.sqrt;
export function square(x) {
return x * x;
}
export function diag(x, y) {
return sqrt(square(x) + square(y));
}
//main.js
import { square, diag } from 'helper';console.log(square(11)); // 121
console.log(diag(4, 3)); // 5
这些导入和导出模块会被webpack自动检测到,并作为指示符,以确定哪些javascript模块将在捆绑包中被包含进去,这有效地消除对“以正确的顺序安排库以及确保正常运行”的焦虑感。
3 . 为什么学Webpack?
下面列举说说学习Webpack对前端开发者来说能够建立哪些工作优势:
3-1 更好地了解现代前端生态系统
你应该注意到,Angular、React和Vue早就依靠webpack来构建boilerplates或现成的应用。
boilerplates要依赖webpack的原因是,它像Angular一样包含许多模块和库,如上所述,Webpack使下载和包含模块的过程实现了自动化,因此它经常被使用在框架/库中。
因此学习Webpack并非仅仅可以使你受益于前面那些好处,而且你还能够了解前端的boilerplates如何在后台运行,从而了解现代的前端生态系统。
3-2 更高效地完成开发
webpack的“热模块更换”功能,让我们节省了时间。具体说,由于页面无需触发完全的重加载就可以将我们的更改表现在javascript代码上,这显然促进了工作效率的提升。
注意,不仅仅适用于javascript,你的CSS代码也可以通过在webpack配置中添加css加载程序,而从中受益。开发变得异常迅速,并减少了调试时页面在完全加载上所花费的时间。
3-3 更好地设置单页应用
当你学习webpack时,你将能够轻松设置单页应用,在React上尤为明显——因为webpack可以依此使用Babel之类的编译器,将JSX语法转换为可读的javascript代码。
3-4 全面控制构建系统
如需要在早期版本中转换ES6 +代码以使javascript代码与旧版浏览器相兼容,必要的话,你可以选择webpack所需的各种构建系统,例如通过webpack加载器使用babel或traceur,。
总结,
如果你已经了解并且会利用Webpack,那么我们已通过此文带你回归了过去,追忆往昔;但是如果你正打算入手,相信全文对你多少能有点帮助吧。