|
作者:wenjuanrao,腾讯PCG前端开发工程师最近梳理了下以前webpack的相关开发经验,整理和总结了一份入门笔记,欢迎大家围观和批评指正。随着web应用越来越复杂和庞大,前端技术迅猛发展,各路大神各显神通,多种优秀的前端框架、新语言和其他相关技术(如下图所示)不断涌现,这些都极大地提高了我们的开发效率。前端技术栈然鹅,我们都知道这些技术都有一个共同点,那就是源代码都无法直接在浏览器上运行。此时,我们就需要通过构建工具将这些代码转换成浏览器可执行的JS、CSS、HTML。这对前端构建工具有了更高的要求。历史上也出现了一系列构建工具,一些常见的如下:常见的构建工具其中,Webpack凭借其强大的功能与良好的使用体验,还有有庞大的社区支持,在众多构建工具中脱颖而出成为时下最流行的构建工具。在言归正传之前,我们先来简单了解一下webpack。webpackWebpack简介根据官网介绍,Webpack是一个用于现代JavaScript应用程序的静态模块打包工具。当webpack处理应用程序时,它会在内部从一个或多个入口点构建一个依赖图(dependencygraph),然后将你项目中所需的每一个模块组合成一个或多个bundles,它们均为静态资源,用于展示你的内容。Webpack一些核心概念:Entry:入口,指示Webpack应该使用哪个模块,来作为构建其内部依赖图(dependencygraph)的开始。Output:输出结果,告诉Webpack在哪里输出它所创建的bundle,以及如何命名这些文件。Module:模块,在Webpack里一切皆模块,一个模块对应着一个文件。Webpack会从配置的Entry开始递归找出所有依赖的模块。Chunk:代码块,一个Chunk由多个模块组合而成,用于代码合并与分割。Loader:模块代码转换器,让webpack能够去处理除了JS、JSON之外的其他类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中。Plugin:扩展插件。在webpack运行的生命周期中会广播出许多事件,plugin可以监听这些事件,在合适的时机通过webpack提供的api改变输出结果。常见的有:打包优化,资源管理,注入环境变量。Mode:模式,告知webpack使用相应模式的内置优化BrowserCompatibility:浏览器兼容性,Webpack支持所有符合ES5标准的浏览器(IE8以上版本)Webpack的作用Webpack的作用非常多,简单列举几点如下:我们可以通过loader和plugin机制去进一步扩展能力,按照项目需要去实现个性化的功能。铺垫了那么多,现在回归主题吧!Webpack是由nodejs编写的前端资源加载/打包工具,由nodejs提供了强大的文件处理,IO能力。Loader和Plugin在Webpack里是支柱能力。在整个构建流程中,Loader和Plugin对编译结果起着决定性的作用,下面主要讲一下Webpack中一些常用的Loader和Plugin。Loader简介webpack中提供了一种处理多种文件格式的机制,这便是Loader,我们可以把Loader当成一个转换器,它可以将某种格式的文件转换成Wwebpack支持打包的模块。在Webpack中,一切皆模块,我们常见的Javascript、CSS、Less、Typescript、Jsx、等文件都是模块,不同模块的加载是通过模块加载器来统一管理的,当我们需要使用不同的Loader来解析不同类型的文件时,我们可以在module.rules字段下配置相关规则。loader特点loader本质上是一个函数,output=loader(input)//input可为工程源文件的字符串,也可是上一个loader转化后的结果;第一个loader的传入参数只有一个:资源文件(resourcefile)的内容;loader支持链式调用,webpack打包时是按照数组从后往前的顺序将资源交给loader处理的。支持同步或异步函数。代码结构代码结构通常如下://source:资源输入,对于第一个执行的loader为资源文件的内容;后续执行的loader则为前一个loader的执行结果// sourceMap: 可选参数,代码的 sourcemap 结构// data: 可选参数,其它需要在 Loader 链中传递的信息,比如 posthtml/posthtml-loader 就会通过这个参数传递参数的 AST 对象const loaderUtils = require('loader-utils');module.exports = function(source, sourceMap?, data?) { // 获取到用户给当前 Loader 传入的 options const options = loaderUtils.getOptions(this); //TODO: 此处为转换source的逻辑 return source;};常用的Loader1.babel-loaderbabel-loader基于babel,用于解析JavaScript文件。babel有丰富的预设和插件,babel的配置可以直接写到options里或者单独写道配置文件里。Babel是一个Javscript编译器,可以将高级语法(主要是ECMAScript2015+)编译成浏览器支持的低版本语法,它可以帮助你用最新版本的Javascript写代码,提高开发效率。webpack通过babel-loader使用Babel。用法:# 环境要求:webpack 4.x || 5.x | babel-loader 8.x | babel 7.x# 安装依赖包:npm install -D babel-loader @babel/core @babel/preset-env webpack然后,我们需要建立一个Babel配置文件来指定编译的规则。Babel配置里的两大核心:插件数组(plugins)和预设数组(presets)。Babel的预设(preset)可以被看作是一组Babel插件的集合,由一系列插件组成。常用预设:@babel/preset-env ES2015+语法@babel/preset-typescript TypeScript@babel/preset-react React@babel/preset-flow Flow插件和预设的执行顺序:插件比预设先执行插件执行顺序是插件数组从前向后执行预设执行顺序是预设数组从后向前执行webpack配置代码:// webpack.config.jsmodule: { rules: [ { test: /\.m?js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: [ ['@babel/preset-env', { targets: "defaults" }] ], plugins: ['@babel/plugin-proposal-class-properties'], // 缓存 loader 的执行结果到指定目录,默认为node_modules/.cache/babel-loader,之后的 webpack 构建,将会尝试读取缓存 cacheDirectory: true, } } } ]}以上options参数也可单独写到配置文件里,许多其他工具都有类似的配置文件:ESLint(.eslintrc)、Prettier(.prettierrc)。配置文件我们一般只需要配置presets(预设数组)和plugins(插件数组),其他一般也用不到,代码示例如下:// babel.config.jsmodule.exports = (api) => { return { presets: [ '@babel/preset-react', [ '@babel/preset-env', { useBuiltIns: 'usage', corejs: '2', targets: { chrome: '58', ie: '10' } } ] ], plugins: [ '@babel/plugin-transform-react-jsx', '@babel/plugin-proposal-class-properties' ] };};推荐阅读:babel配置文件相关文档插件手册2.ts-loader为webpack提供的TypeScriptloader,打包编译Typescript。安装依赖:npm install ts-loader --save-devnpm install typescript --devwebpack配置如下:// webpack.config.jsonmodule.exports = { mode: "development", devtool: "inline-source-map", entry: "./app.ts", output: { filename: "bundle.js" }, resolve: { // Add `.ts` and `.tsx` as a resolvable extension. extensions: [".ts", ".tsx", ".js"] }, module: { rules: [ // all files with a `.ts` or `.tsx` extension will be handled by `ts-loader` { test: /\.tsx?$/, loader: "ts-loader" } ] }};还需要typescript编译器的配置文件tsconfig.json:{ "compilerOptions": { // 目标语言的版本 "target": "esnext", // 生成代码的模板标准 "module": "esnext", "moduleResolution": "node", // 允许编译器编译JS,JSX文件 "allowJS": true, // 允许在JS文件中报错,通常与allowJS一起使用 "checkJs": true, "noEmit": true, // 是否生成source map文件 "sourceMap": true, // 指定jsx模式 "jsx": "react" }, // 编译需要编译的文件或目录 "include": [ "src", "test" ], // 编译器需要排除的文件或文件夹 "exclude": [ "node_modules", "**/*.spec.ts" ]}更多配置请看官网3.markdown-loadermarkdown编译器和解析器用法:只需将loader添加到您的配置中,并设置options。js代码里引入markdown文件:// file.jsimport md from 'markdown-file.md';console.log(md);webpack配置:// wenpack.config.jsconst marked = require('marked');const renderer = new marked.Renderer();module.exports = { // ... module: { rules: [ { test: /\.md$/, use: [ { loader: 'html-loader' }, { loader: 'markdown-loader', options: { pedantic: true, renderer } } ] } ], },};4.raw-loader可将文件作为字符串导入:// app.jsimport txt from './file.txt';// webpack.config.jsmodule.exports = { module: { rules: [ { test: /\.txt$/, use: 'raw-loader' } ] }}5.file-loader用于处理文件类型资源,如jpg,png等。返回值为publicPath为准:// file.jsimport img from './webpack.png';console.log(img); // 编译后:https://www.tencent.com/webpack_605dc7bf.png// webpack.config.jsmodule.exports = { module: { rules: [ { test: /\.(png|jpe?g|gif)$/i, loader: 'file-loader', options: { name: '[name]_[hash:8].[ext]', publicPath: "https://www.tencent.com", }, }, ], },};css文件里的路径变成如下:/* index.less */.tag { background-color: red; background-image: url(./webpack.png);}/* 编译后:*/background-image: url(https://www.tencent.com/webpack_605dc7bf.png);6.url-loader它与file-loader作用相似,也是处理的,只不过url-loader可以设置一个根据大小进行不同的操作,如果该大小大于指定的大小,则将进行打包资源,否则将转换为base64字符串合并到js文件里。module.exports = { module: { rules: [ { test: /\.(png|jpg|jpeg)$/, use: [ { loader: 'url-loader', options: { name: '[name]_[hash:8].[ext]', // 这里单位为(b) 10240 => 10kb // 这里如果小于10kb则转换为base64打包进js文件,如果大于10kb则打包到对应目录 limit: 10240, } } ] } ] }}7.svg-sprite-loader会把引用的svg文件塞到一个个symbol中,合并成一个大的SVGsprite,使用时则通过SVG的传入图标id后渲染出图标。最后将这个大的svg放入body中。symbol的id如果不特别指定,就是你的文件名。该loader可以搭配svgo-loader一起使用,svgo-loader是svg的优化器,它可以删除和修改SVG元素,折叠内容,移动属性等,具体不展开描述。感兴趣的可以移步官方介绍。用途:可以用来开发统一的图标管理库。效果示例代码:// js文件里用法import webpack from './webpack/webpack.svg';const type = 'webpack';const svg = ` `;const dom = ` ${svg} `;document.getElementById('react-app').innerHTML = dom;// webpack.config.jsmodule.exports = { module: { rules: [ { test: /\.(png|jpg|jpeg)$/, use: [ { test: /\.svg$/, use: [ { loader: 'svg-sprite-loader' }, 'svgo-loader' ] }, ] } ] }}原理:利用svg的symbol元素,将每个icon包裹在symbol中,通过use使用该symbol。8.style-loader通过注入标签的方式引入CSS的,加载会更快;不要将style-loader和mini-css-extract-plugin针对同一个CSS模块一起使用!代码示例见下文postcss-loader。9.css-loader仅处理css的各种加载语法(@import和url()函数等),就像js解析import/require()一样。代码示例见下文postcss-loader。10.postcss-loaderPostCSS是一个允许使用JS插件转换样式的工具。这些插件可以检查(lint)你的CSS,支持CSSVariables和Mixins,编译尚未被浏览器广泛支持的先进的CSS语法,内联,以及其它很多优秀的功能。PostCSS在业界被广泛地应用。PostCSS的autoprefixer插件是最流行的CSS处理工具之一。autoprefixer添加了浏览器前缀,它使用CanIUse上面的数据。安装npm install postcss-loader autoprefixer --save-dev代码示例:// webpack.config.jsconst MiniCssExtractPlugin = require('mini-css-extract-plugin');const isDev = process.NODE_ENV === 'development';module.exports = { module: { rules: [ { test: /\.(css|less)$/, exclude: /node_modules/, use: [ isDev ? 'style-loader' : MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { importLoaders: 1, } }, { loader: 'postcss-loader' }, { loader: 'less-loader', options: { lessOptions: { javascriptEnabled: true } } } ] } ] }}然后在项目根目录创建postcss.config.js,并且设置支持哪些浏览器,必须设置支持的浏览器才会自动添加添加浏览器兼容:module.exports = { plugins: [ require('precss'), require('autoprefixer')({ 'browsers': [ 'defaults', 'not ie 1%', 'iOS 7', 'last 3 iOS versions' ] }) ]}截止到目前,PostCSS有200多个插件。你可以在插件列表或搜索目录找到它们。了解更多请移步链接11.less-loader解析less,转换为css。代码示例见上文postcss-loader了解更多请移步链接。PluginPlugin简介Webpack就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。Webpack通过Tapable来组织这条复杂的生产线。Webpack在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。Webpack的事件流机制保证了插件的有序性,使得整个系统扩展性很好。——「深入浅出Webpack」常用Plugin1.copy-webpack-plugin将已经存在的单个文件或整个目录复制到构建目录。const CopyPlugin = require("copy-webpack-plugin");module.exports = { plugins: [ new CopyPlugin({ patterns: [ { from: './template/page.html', to: `${__dirname}/output/cp/page.html` }, ], }), ],};2.html-webpack-plugin基本作用是生成html文件:单页应用可以生成一个html入口,多页应用可以配置多个html-webpack-plugin实例来生成多个页面入口;为html引入外部资源如script、link,将entry配置的相关入口chunk以及mini-css-extract-plugin抽取的css文件插入到基于该插件设置的template文件生成的html文件里面,具体的方式是link插入到head中,script插入到head或body中。const HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = { entry: { news: [path.resolve(__dirname, '../src/news/index.js')], video: path.resolve(__dirname, '../src/video/index.js'), }, plugins: [ new HtmlWebpackPlugin({ title: 'news page', // 生成的文件名称 相对于webpackConfig.output.path路径而言 filename: 'pages/news.html', // 生成filename的文件模板 template: path.resolve(__dirname, '../template/news/index.html'), chunks: ['news'] }), new HtmlWebpackPlugin({ title: 'video page', // 生成的文件名称 filename: 'pages/video.html', // 生成filename的文件模板 template: path.resolve(__dirname, '../template/video/index.html'), chunks: ['video'] }), ]};3.clean-webpack-plugin默认情况下,这个插件会删除webpack的output.path中的所有文件,以及每次成功重新构建后所有未使用的资源。这个插件在生产环境用的频率非常高,因为生产环境经常会通过hash生成很多bundle文件,如果不进行清理的话每次都会生成新的,导致文件夹非常庞大。const { CleanWebpackPlugin } = require('clean-webpack-plugin');module.exports = { plugins: [ new CleanWebpackPlugin(), ]};4.mini-css-extract-plugin本插件会将CSS提取到单独的文件中,为每个包含CSS的JS文件创建一个CSS文件。// 建议 mini-css-extract-plugin 与 css-loader 一起使用// 将 loader 与 plugin 添加到 webpack 配置文件中const MiniCssExtractPlugin = require('mini-css-extract-plugin');module.exports = { plugins: [new MiniCssExtractPlugin()], module: { rules: [ { test: /\.css$/i, use: [MiniCssExtractPlugin.loader, 'css-loader'], }, ], },};可以结合上文关于style-loader的介绍一起了解该插件。5.webpack.HotModuleReplacementPlugin模块热替换插件,除此之外还被称为HMR。该功能会在应用程序运行过程中,替换、添加或删除模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度:保留在完全重新加载页面期间丢失的应用程序状态;只更新变更内容,以节省宝贵的开发时间;在源代码中CSS/JS产生修改时,会立刻在浏览器中进行更新,这几乎相当于在浏览器devtools直接更改样式。启动方式有2种:引入插件webpack.HotModuleReplacementPlugin并且设置devServer.hot:true命令行加--hot参数package.json配置:{ "scripts": { "start": "NODE_ENV=development webpack serve --progress --mode=development --config=scripts/dev.config.js --hot" }}webpack的配置如下:// scripts/dev.config.js文件const webpack = require('webpack');const path = require('path');const outputPath = path.resolve(__dirname, './output/public');module.exports = { mode: 'development', entry: { preview: [ './node_modules/webpack-dev-server/client/index.js?path=http://localhost:9000', path.resolve(__dirname, '../src/preview/index.js') ], }, output: { filename: 'static/js/[name]/index.js', // 动态生成的chunk在输出时的文件名称 chunkFilename: 'static/js/[name]/chunk_[chunkhash].js', path: outputPath }, plugins: [ // 大多数情况下不需要任何配置 new webpack.HotModuleReplacementPlugin(), ], devServer: { // 仅在需要提供静态文件时才进行配置 contentBase: outputPath, // publicPath: '', // 值默认为'/' compress: true, port: 9000, watchContentBase: true, hot: true, // 在服务器启动后打开浏览器 open: true, // 指定打开浏览器时要浏览的页面 openPage: ['pages/preview.html'], // 将产生的文件写入硬盘。 写入位置为output.path配置的目录 writeToDisk: true, }}注意:HMR绝对不能被用在生产环境。6.webpack.DefinePlugin创建一个在编译时可以配置的全局常量。这会对开发模式和生产模式的构建允许不同的行为非常有用。因为这个插件直接执行文本替换,给定的值必须包含字符串本身内的实际引号。通常,有两种方式来达到这个效果,使用'"production"',或者使用JSON.stringify('production')// webpack.config.jsconst isProd = process.env.NODE_ENV === 'production';module.exports = { plugins: [ new webpack.DefinePlugin({  AGE_URL: JSON.stringify(isProd ? 'https://www.tencent.com/page' : 'http://testsite.tencent.com/page' ) }), ]}// 代码里面直接使用console.log(PAGE_URL);7.webpack-bundle-analyzer可以看到项目各模块的大小,可以按需优化.一个webpack的bundle文件分析工具,将bundle文件以可交互缩放的treemap的形式展示。const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;module.exports = { plugins: [ new BundleAnalyzerPlugin() ]}启动服务:生产环境查看:NODE_ENV=productionnpmrunbuild开发环境查看:NODE_ENV=developmentnpmrunstart最终效果:了解更多请移步链接8.SplitChunksPlugin代码分割。module.exports = { optimization: { splitChunks: { // 分隔符 // automaticNameDelimiter: '~', // all, async, and initial chunks: 'all', // 它可以继承/覆盖上面splitChunks中所有的参数值,除此之外还额外提供了三个配置,分别为:test,priority和reuseExistingChunk cacheGroups: { vendors: { // 表示要过滤 modules,默认为所有的 modules,可匹配模块路径或 chunk 名字,当匹配的是 chunk 名字的时候,其里面的所有 modules 都会选中 test: /[\\/]node_modules\/antd\//, //priority:表示抽取权重,数字越大表示优先级越高。因为一个module可能会满足多个cacheGroups的条件,那么抽取到哪个就由权重最高的说了算; // priority: 3, //reuseExistingChunk:表示是否使用已有的chunk,如果为true则表示如果当前的chunk包含的模块已经被抽取出去了,那么将不会重新生成新的。 reuseExistingChunk: true, name: 'antd' } } } },}腾讯程序员视频号直播来了!腾讯程序员视频号最新视频一键轻松转换PDF与Office
|
|