在这个内卷为王的时代,内卷文化曾经渗透到工作和生存的方方面面。不在常识的陆地里飞翔,就会在常识的陆地里溺亡。作为一名新生代农民工,在智商与怠惰曾经被工友大佬们双重碾压的同时,面对突飞猛进的搬砖(编程)技能,学习的速度曾经赶不上忘记的速度,可是还得强忍泪水拥抱变动、一直打怪降级。

最近梳理了下以前webpack的相干开发教训,整顿和总结了一份入门笔记。欢送大家围观和批评指正。

随着web利用越来越简单和宏大,前端技术迅猛发展,各路大神各显神通,多种优良的前端框架、新语言和其余相干技术(如下图所示)不断涌现,这些都极大地提高了咱们的开发效率

然鹅,咱们都晓得这些技术都有一个共同点,那就是源代码都无奈间接在浏览器上运行。此时,咱们就须要通过构建工具将这些代码转换成浏览器可执行的JS、CSS、HTML。这对前端构建工具有了更高的要求。

历史上也呈现了一系列构建工具,一些常见的如下:

其中,Webpack凭借其弱小的性能与良好的应用体验,还有有宏大的社区反对,在泛滥构建工具中怀才不遇成为时下最风行的构建工具。

在言归正传之前,咱们先来简略理解一下webpack。

Webpack简介

依据官网介绍,Webpack 是一个用于古代 JavaScript 应用程序的 动态模块打包工具。当 webpack 解决应用程序时,它会在外部从一个或多个入口点构建一个 依赖图(dependency graph),而后将你我的项目中所需的每一个模块组合成一个或多个 bundles,它们均为动态资源,用于展现你的内容。

Webpack一些外围概念:

  • Entry:入口,批示 Webpack 应该应用哪个模块,来作为构建其外部 依赖图(dependency graph) 的开始。
  • Output:输入后果,通知 Webpack 在哪里输入它所创立的 bundle,以及如何命名这些文件。
  • Module:模块,在 Webpack 里所有皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
  • Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与宰割。
  • Loader:模块代码转换器,让webpack可能去解决除了JS、JSON之外的其余类型的文件,并将它们转换为无效 模块,以供应用程序应用,以及被增加到依赖图中。
  • Plugin:扩大插件。在webpack运行的生命周期中会播送出许多事件,plugin能够监听这些事件,在适合的机会通过webpack提供的api扭转输入后果。常见的有:打包优化,资源管理,注入环境变量。
  • Mode:模式,告知 webpack 应用相应模式的内置优化
  • Browser Compatibility:浏览器兼容性,Webpack 反对所有合乎 ES5 规范 的浏览器(IE8以上版本)

Webpack的作用

Webpack的作用十分多,简略列举几点如下:

<div align=center><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fca3177d53eb4f98b60612cd544dfe45~tplv-k3u1fbpfcp-watermark.image"/></div>

咱们能够通过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 的传入参数只有一个:资源文件(resource file)的内容;
  • 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;};

罕用的Loader

1. babel-loader

babel-loader基于babel,用于解析JavaScript文件。babel有丰盛的预设和插件,babel的配置能够间接写到options里或者独自写道配置文件里。

Babel是一个Javscript编译器,能够将高级语法(次要是ECMAScript 2015+ )编译成浏览器反对的低版本语法,它能够帮忙你用最新版本的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提供的 TypeScript loader,打包编译Typescript

装置依赖:

npm install ts-loader --save-devnpm install typescript --dev

webpack配置如下:

// 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-loader

markdown编译器和解析器

用法:

只需将 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 中,合并成一个大的SVG sprite,应用时则通过 SVG 的 \<use> 传入图标 id 后渲染出图标。最初将这个大的 svg 放入 body 中。symbol的id如果不特地指定,就是你的文件名。

该loader能够搭配svgo-loader 一起应用,svgo-loader是svg的优化器,它能够删除和批改SVG元素,折叠内容,挪动属性等,具体不开展形容。感兴趣的能够移步 官网介绍。

*用处:能够用来开发对立的图标治理库。 *

*示例代码: *

// js文件里用法import webpack from './webpack/webpack.svg';const type = 'webpack';const svg =  `<svg>    <use xlink:href="#${type}"/>  </svg>`;const dom = `<div class="tag">  ${svg}  </div>`;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

通过注入\<style\>标签将CSS插入到DOM中

留神:

  • 如果因为某些起因你须要将CSS提取为一个文件(即不要将CSS存储在JS模块中),此时你须要应用插件 mini-css-extract-plugin(前面的Pugin局部会介绍);
  • 对于development模式(包含 webpack-dev-server)你能够应用style-loader,因为它是通过\<style>\</style>标签的形式引入CSS的,加载会更快;
  • 不要将 style-loader 和 mini-css-extract-plugin 针对同一个CSS模块一起应用!

代码示例见下文 postcss-loader

9. css-loader

仅解决css的各种加载语法(@import和url()函数等),就像 js 解析 import/require() 一样

代码示例见下文 postcss-loader

10. postcss-loader

PostCSS 是一个容许应用 JS 插件转换款式的工具。 这些插件能够查看(lint)你的 CSS,反对 CSS Variables 和 Mixins, 编译尚未被浏览器广泛支持的先进的 CSS 语法,内联图片,以及其它很多优良的性能。

PostCSS 在业界被宽泛地利用。PostCSS 的 autoprefixer 插件是最风行的 CSS 解决工具之一。

autoprefixer 增加了浏览器前缀,它应用 Can I Use 下面的数据。

装置

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 < 11',        'last 2 versions',        '> 1%',        'iOS 7',        'last 3 iOS versions'      ]    })  ]}

截止到目前,PostCSS 有 200 多个插件。你能够在 插件列表 或 搜寻目录 找到它们

理解更多请移步 链接

11. less-loader

解析less,转换为css

代码示例见上文 postcss-loader

理解更多请移步 链接

Plugin

Plugin简介

Webpack 就像一条生产线,要通过一系列解决流程后能力将源文件转换成输入后果。 这条生产线上的每个解决流程的职责都是繁多的,多个流程之间有存在依赖关系,只有实现以后解决后能力交给下一个流程去解决。 插件就像是一个插入到生产线中的一个性能,在特定的机会对生产线上的资源做解决。

Webpack 通过 Tapable 来组织这条简单的生产线。 Webpack 在运行过程中会播送事件,插件只须要监听它所关怀的事件,就能退出到这条生产线中,去扭转生产线的运作。 Webpack 的事件流机制保障了插件的有序性,使得整个零碎扩展性很好。

——「深入浅出 Webpack」

罕用Plugin

1. 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({      PAGE_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=production npm run build
  • 开发环境查看:NODE_ENV=development npm run start

最终成果:

理解更多请移步 链接

8. SplitChunksPlugin

代码宰割。

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;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'        }      }    }  },}