打包工具(构建工具)

329 阅读14分钟

构建工具的作用

  1. 模块化开发支持: 支持直接从node_modules里引入代码 + 多种模块化支持
  2. 处理代码兼容性: 比如babel语法降级, less,ts 语法转换(不是构建工具做的, 构建工具将这些语法对应的处理工具集成进来自动化处理)
  3. 提高项目性能: 压缩文件, 代码分割
  4. 优化开发体验:
    • 构建工具会帮你自动监听文件的变化, 当文件变化以后自动帮你调用对应的集成工具进行重新打包, 然后再浏览器重新运行(整个过程叫做热更新, hot replacement)
    • 开发服务器: 跨域的问题, 用react-cli create-react-element vue-cli  解决跨域的问题

构建工具他让我们可以不用每次都关心我们的代码在浏览器如何运行, 我们只需要首次给构建工具提供一个配置文件(这个配置文件也不是必须的, 如果你不给他 他会有默认的帮你去处理), 有了这个集成的配置文件以后, 我们就可以在下次需要更新的时候调用一次对应的命令就好了, 如果我们再结合热更新, 我们就更加不需要管任何东西。

主流构建工具

  • webpack
  • vite
  • parcel
  • esbuild
  • rollup
  • grunt
  • gulp

专业名词

开箱即用(out of box): 你不需要做任何额外的配置就可以使用vite来帮你处理构建工作

webpack与vite

webpack更多的关注兼容性, 而vite关注浏览器端的开发体验

浏览器与服务器

浏览器加载一个index.js以及例如moment.js是通过网络请求来获取的,而在服务器端是通过读本地文件来获取的(获取node_module的方式不同)

vite

vite开箱即用 vite处理的过程中如果说看到了有非绝对路径或者相对路径的引用, 他则会尝试开启路径补全

import _ from "lodash"; // lodash可能也import了其他的东西

它会先找当前模块下的node_modules,没找到会找上级目录,然后一直到user/node_modules

为什么vite.config.js可以书写成esmodule的形式? 因为vite他在读取这个vite.config.js的时候会率先node去解析文件语法, 如果发现你是esmodule规范会直接将你的esmodule规范进行替换变成commonjs规范

生产和开发

yarn dev ---> 开发(每次依赖预构建所重新构建的相对路径都是正确的) yarn dev === yarn dev -mode develop vite会全权交给一个叫做rollup的库去完成生产环境的打包

依赖预构建

因为不同的库有些的导出的方式是commonjs,有些是esmodule,有些是es3,es5立即执行函数的方式所以vite要进行依赖预构建

首先vite会找到对应的依赖, 然后调用esbuild(对js语法进行处理的一个库), 将其他规范的代码转换成esmodule规范, 然后放到当前目录下的node_modules/.vite/deps, 同时对esmodule规范的各个模块进行统一集成

他解决了3个问题:

  1. 不同的第三方包会有不同的导出格式(这个是vite没法约束人家的事情)
  2. 对路径的处理上可以直接使用.vite/deps, 方便路径重写
  3. 叫做网络多包传输的性能问题(也是原生esmodule规范不敢支持node_modules的原因之一), 有了依赖预构建以后无论他有多少的额外export 和import, vite都会尽可能的将他们进行集成最后只生成一个或者几个模块

create-vite和vite的区别

比如我们敲了yarn create vite

  1. 帮我们全局安装一个东西: create-vite (vite的脚手架)
  2. 直接运行这个create-vite bin目录的下的一个执行配置

create-vite和vite的关系是什么呢? ---- create-vite内置了vite

使用yarn create vite my-vue-app --template vue其实是create-vite在所做的事情

预设: 买房子 毛坯房(我们的工程) 买沙发, 做装修, 修各个厕所, 埋线, 精装修的房: 搭建好了

我们自己搭建一个项目: 下载vite, vue, post-css, less, babel

vue-cli/create-react-app(开发商)给我们提供已经精装修的模板: 帮你把react/vue都下好了, 同时他还帮你把配置调整到了最佳实践

create-vite(开发商)给你一套精装修模板(给你一套预设): 下载vite, vue, post-css, less, babel好了, 并且给你做好了最佳实践的配置

webpack

webpack编译原理

webpack的编译原理, AST 抽象语法分析的工具 分析出你写的这个js文件有哪些导入和导出操作 因为webpack支持多种模块化, 他一开始必须要统一模块化代码, 所以意味着他需要将所有的依赖全部读一遍(webpack的打包和热更新的速度是慢的)

webpack打包原理是什么

webpack打包原理是将根据文件间的依赖关系对其进行静态分析,然后将这些模块按指定规则生成静态资源,当 webpack处理程序时,会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将所所有这些模块打包成bundle。

将根据文件间的依赖关系对其进行静态分析,然后将这些模块按指定规则生成静态资源,当 webpack 处理程序时,会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。

webpack只是一个打包模块的机制,只是把依赖的模块转化成可以代表这些包的静态文件。并不是什么commonjs或者amd之类的模块化规范。webpack就是识别你的入口文件。识别你的模块依赖,来打包你的代码。

至于你的代码使用的是commonjs还是amd或者es6的import。webpack都会对其进行分析。来获取代码的依赖。

AMD、CMD、CommonJS和ES6 Import四个都是引用js文件的方法,具体实现和使用方式不同

webpack做的就是分析代码。转换代码,编译代码,输出代码。webpack本身是一个node的模块,所以webpack.config.js是以commonjs形式书写的(node中的模块化是commonjs规范的)

webpack中每个模块有一个唯一的id,是从0开始递增的。整个打包后的bundle.js是一个匿名函数自执行。参数则为一个数组。数组的每一项都为个function。function的内容则为每个模块的内容,并按照require的顺序排列。

1、Entry

入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。每个依赖项随即被处理,最后输出到称之为 bundles 的文件中。

2、Output

output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist。基本上,整个应用程序结构,都会被编译到指定的输出路径的文件夹中。

3、Module

模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。

4、Chunk

代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。

5、Loader

loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。

loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后就可以利用 webpack 的打包能力,对它们进行处理。

本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块。

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/js/index.js',  //入口
  output: {                     //出口
    filename: 'js/[name].js',
    path: resolve(__dirname, 'build')
  },
  module: {                  //模块
    rules: [                 
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [new HtmlWebpackPlugin()], //插件
  mode: 'development',            //模式
  resolve: {              //除css以外
    alias: {
      $css: resolve(__dirname, 'src/css')
    },
    extensions: ['.js', '.json', '.jsx', '.css'],
    modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
  },
  devServer: {         //服务器配置
    // 运行代码的目录
    contentBase: resolve(__dirname, 'build'),
    // 监视 contentBase 目录下的所有文件,一旦文件变化就会 reload
    watchContentBase: true,
    watchOptions: {
      // 忽略文件
      ignored: /node_modules/
    },
    // 启动gzip压缩
    compress: true,
    // 端口号
    port: 5000,
    // 域名
    host: 'localhost',
    // 自动打开浏览器
    open: true,
    // 开启HMR功能
    hot: true,
    // 不要显示启动服务器日志信息
    clientLogLevel: 'none',
    // 除了一些基本启动信息以外,其他内容都不要显示
    quiet: true,
    // 如果出错了,不要全屏提示~
    overlay: false,
    // 服务器代理 --> 解决开发环境跨域问题
    proxy: {
      // 一旦devServer(5000)服务器接受到 /api/xxx 的请求,就会把请求转发到另外一个服务器(3000)
      '/api': {
        target: 'http://localhost:3000',
        // 发送请求时,请求路径重写:将 /api/xxx --> /xxx (去掉/api)
        pathRewrite: {
          '^/api': ''
        }
      }
    }
  }
};

优缺点

优点

  1. 专注于处理模块化的项目,能做到开箱即用,一步到位
  2. 可通过 plugin 扩展,完整好用又不失灵活
  3. 使用场景不局限于 web 开发
  4. 社区庞大活跃,经常引入紧跟时代发展的新特性,能为大多数场景找到已有的开源扩展
  5. 良好的开发体验

缺点 只能用于采用模块化开发的项目

npm 打包时需要注意哪些?如何利用 webpack 来更好的构建?

npm 是目前最大的 JavaScript 模块仓库,里面有来自全世界开发者上传的可复用模块。你可能只是 JS 模块的使用者,但是有些情况你也会去选择上传自己开发的模块。 关于 NPM 模块上传的方法可以去官网上进行学习,这里只讲解如何利用webpack来构建。

NPM模块需要注意以下问题:

要支持 CommonJS 模块化规范,所以要求打包后的最后结果也遵守该规则。 Npm模块使用者的环境是不确定的,很有可能并不支持ES6,所以打包的最后结果应该是采用ES5编写的。并且如果ES5是经过转换的,请最好连同SourceMap一同上传。 Npm包大小应该是尽量小(有些仓库会限制包大小) 发布的模块不能将依赖的模块也一同打包,应该让用户选择性的去自行安装。这样可以避免模块应用者再次打包时出现底层模块被重复打包的情况。 UI组件类的模块应该将依赖的其它资源文件,例如.css文件也需要包含在发布的模块里。 基于以上需要注意的问题,我们可以对于webpack配置做以下扩展和优化:

CommonJS模块化规范的解决方案: 设置output.libraryTarget='commonjs2'使输出的代码符合CommonJS2 模块化规范,以供给其它模块导入使用 输出ES5代码的解决方案:使用babel-loader把 ES6 代码转换成 ES5 的代码。再通过开启devtool: 'source-map'输出SourceMap以发布调试。 Npm包大小尽量小的解决方案:Babel 在把 ES6 代码转换成 ES5 代码时会注入一些辅助函数,最终导致每个输出的文件中都包含这段辅助函数的代码,造成了代码的冗余。解决方法是修改.babelrc文件,为其加入transform-runtime插件 不能将依赖模块打包到NPM模块中的解决方案:使用externals配置项来告诉webpack哪些模块不需要打包。 对于依赖的资源文件打包的解决方案:通过css-loader和extract-text-webpack-plugin来实现

什么是 loader ? 什么是 plugin ?

【Loader】:用于对模块源码的转换,loader描述了webpack如何处理非javascript模块,并且在buld中引入这些依赖。loader可以将文件从不同的语言(如TypeScript)转换为JavaScript,或者将内联图像转换为data URL。比如说:CSS-Loader,Style-Loader等。

【Plugin】:目的在于解决loader无法实现的其他事,从打包优化和压缩,到重新定义环境变量,功能强大到可以用来处理各种各样的任务。webpack提供了很多开箱即用的插件:CommonChunkPlugin主要用于提取第三方库和公共模块,避免首屏加载的bundle文件,或者按需加载的bundle文件体积过大,导致加载时间过长,是一把优化的利器。而在多页面应用中,更是能够为每个页面间的应用程序共享代码创建bundle。

loader:模块转换器,webpack 将一切文件视为模块,但 webpack 只能解析 JavaScript 文件,而 loader 作用是让 webpack 拥有了加载 和 解析非 JavaScript 文件的能力。

plugin:在 webpack 构建流程中的特定时机注入扩展逻辑,让它具有更多的灵活性。在 webpack 运行的生命周期中会广播出许多事件,plugin 可以监听这些事件,在合适的时机通过 webpack 提供的 API 改变输出结果。

用法的区别:

Loadermodule.rules 中配置,也就是说他作为模块的解析规则而存在。 类型为数组,每一项都是一个Object,里面描述了对于什么类型的文件(test),使用什么加载(loader)和使用的参数(options) Pluginplugins 中单独配置。 类型为数组,每一项是一个 plugin 的实例,参数都通过构造函数传入。

有哪些常见的 Loader ?他们是解决什么问题的?

file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去 source-map-loader:加载额外的 Source Map 文件,以方便断点调试 image-loader:加载并且压缩图片文件 babel-loader:把 ES6 转换成 ES5 css-loader:加载 CSS,支持模块化、压缩、文件导入等特性 style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。 eslint-loader:通过 ESLint 检查 JavaScript 代码 svg-inline-loader:将压缩后的 SVG 内容注入代码中 json-loader : 加载 JSON 文件(默认包含) ts-loader : 将 TypeScript 转换成 JavaScript awesome-typescript-loader:将 TypeScript 转换成 JavaScript,性能优于 ts-loader sass-loader:将 CSS 代码注入 JavaScript 中,通过 DOM 操作去加载 CSS postcss-loader:扩展 CSS 语法,使用下一代 CSS,可以配合 autoprefixer 插件自动补齐 CSS3 前缀 tslint-loader:通过 TSLint检查 TypeScript 代码 vue-loader:加载 Vue.js 单文件组件

有哪些常见的 Plugin?他们是解决什么问题的?

define-plugin:定义环境变量 commons-chunk-plugin:提取公共代码 terser-webpack-plugin : 支持压缩 ES6 (Webpack4) ignore-plugin:忽略部分文件 html-webpack-plugin:简化 HTML 文件创建 (依赖于 html-loader) web-webpack-plugin:可方便地为单页应用输出 HTML,比 html-webpack-plugin 好用 mini-css-extract-plugin : 分离样式文件,CSS 提取为独立文件,支持按需加载 serviceworker-webpack-plugin:为网页应用增加离线缓存功能 clean-webpack-plugin : 删除打包文件 happypack:实现多线程加速编译

如何利用 webpack 来优化前端性能?

webpack 优化前端性能是指优化 webpack 的输出结果,让打包的最终结果在浏览器运行快速高效。

1.压缩代码。删除多余的代码、注释、简化代码的写法等等方式。 用 UglifyJsPluginParallelUglifyPlugin  压缩JS文件 用 mini-css-extract-plugin 压缩 CSS

  1. 利用 CDN 加速。在构建过程中,将引用的静态资源路径修改为CDN上对应的路径。可以利用 webpack 对于output 参数和各 loaderpublicPath 参数来修改资源路径
  2. 删除死代码。JS 用 Tree Shaking,CSS 需要使用 Purify-CSS
  3. 提取公共代码。用 CommonsChunkPlugin 插件

分别介绍 bundle,chunk,module 是什么

bundle:是由 webpack 打包出来的文件, chunk:代码块,一个 chunk 由多个模块组合而成,用于代码的合并和分割。 module:是开发中的单个模块,在 webpack 的世界,一切皆模块,一个模块对应一个文件,webpack 会从配置的 entry 中递归开始找出所有依赖的模块。