1. 以现有脚手架结构对 Vue 框架进行支持

接着上文 webpack 项目进行改造。

1.1 引入 vue 依赖并搭建 vue 项目结构

1.1.1 调整目录结构

按 vue 项目规划目录结构
image.png
调整 webpack 打包入口及配置关键插件 html-webpack-plugin 自动引入 js 进入模板文件

  1. //entry做如下修改
  2. entry:{
  3. main:'./src/main.js'
  4. },
  5. //plugins做如下修改
  6. plugins:[
  7. new HtmlWebpackPlugin({
  8. template:'./public/index.html',
  9. filename:'index.html',
  10. chunks:['main']
  11. })
  12. ]

一个可用 webpack 打包且目录结构基于 vue 的项目就搭建好了。

1.1.2 安装 vue 依赖

安装 vue2 核心库。

  1. npm i vue@2 -S

安装完毕之后我们改造 public 中的模板文件,vue2是替换<div id="app"></div>中的内容,而 vue3 是在标签内追加内容。

  1. <body>
  2. <div id="app"></div>
  3. </body>

然后我们在src下创建一个根路由 App.vue,修改 main.js 为 vue 的入口文件

  1. <template>
  2. <div class="app">
  3. <h1>APP</h1>
  4. </div>
  5. </template>
  6. <script>
  7. export default {
  8. name: App,
  9. }
  10. </script>
  11. <style>
  12. </style>
  1. import Vue from 'vue'
  2. import App from './App.vue'
  3. new Vue({
  4. render: h => h(App)
  5. }).$mount('#app')

一个 vue 项目基础搭建完了,但显然 webpack 构建会失败,因为 webpack 是无法解析.vue 文件的,所以还需要一个加载器。

1.2 引入 vue-loader

根据 vue-loader 官网文档下载 vue-loader,vue-template-compiler 安装并在 webpack.base.js 中配置。

  • vue-template-complier 用来解析 .vue 文件中的模板,动态创建标签,所以页面其实都是 js 生成的。
    1. npm i vue-loader@15 vue-template-compiler@2 -D
    ```javascript const { VueLoaderPlugin } = require(‘vue-loader’)

module.exports = { module: { rules: [ // … 其它规则 { test: /.vue$/, loader: ‘vue-loader’ } ] }, plugins: [ // 请确保引入这个插件! new VueLoaderPlugin() ] }

  1. > vue-loader 不能安装17.0.0版本,会报错 Error: Cannot find module 'vue/compiler-sfc'
  2. 至此 webpack 已经具备了打包构建 vue 项目的能力。
  3. 接下来可继续完善项目,比如引入项目的页面管理框架 VueRouter 。首先安装 VueRouter
  4. ```bash
  5. npm i vue-router@3 -S

vue-router 结合到 vue 项目中的具体使用不再赘述,这属于 vue 的范畴。

构建时的静态资源处理方案(跳过)

这个问题我无法复现,url-loader 能将public 的图片打包到 dist 目录。这个段落只做暂时保留。
存在的问题:
对于标签引入的方式,在 public 中被使用的图片,它们的路径地址写在 html 标签上,webpack 并不知道这张图片是否被依赖,所以并不会打包 public 中的静态资源到 dist 目录。
模块化引入的静态资源因为是 js 代码引入,所以 webpack 能识别,会将静态资源打包到 dist 目录。

解决办法:
前文我们手动引入静态资源,复制 public 文件夹到打包目录 dist 解决问题,其实可以利用 copy-webpack-plugin这个插件来自动完成这个过程,将 public 的内容合并到 dist 中。这样所有的静态资源在构建之后都能正常使用了。

npm i copy-webpack-plugin -D
const CopyPlugin = require('copy-webpack-plugin');

plugins:[
  new CopyPlugin({
    patterns: [
      { 
        // 从哪里复制
        from: path.resolve(__dirname,'public'),
        // 复制到哪里去
        to: path.resolve(__dirname,'dist'),
        globOptions: {
          ignore: ['**/index.html'] // 忽略index.html文件,防止重名冲突
        }
      },
    ],
    options: {
      // 复制粘贴是个 io 操作,规定 io 并发数,也就是能同时复多少个文件
      concurrency: 100,
    },
  })
    ]

优化方案之压缩传输

gzip 压缩代码

webpack 本身会压缩代码,变量改短,空格删掉等等,这是混淆压缩。

gzip 就是打了一个压缩包,就和我们平时压缩文件一样。这里优化访问速度,把文件压缩了再传输。

压缩了的文件传输到浏览器,浏览器接收再解压后读取使用和直接传输文件,浏览器直接使用相比,那不是多了一步解压的操作吗,怎么能算是优化呢?

因为传输大文件耗时比浏览器解压耗时大的多,所以传输压缩包再解压是有意义的优化。节省了流量,节省了带宽。不过要前后端一起开启压缩传输协议才能让浏览器优先获取压缩包传输回来。

引入 compression-webpack-plugin 组件来实现对 vue 项目的代码进一步压缩

npm i compression-webpack-plugin -D

配置插件:

const CompressionPlugin = require("compression-webpack-plugin")
plugins:[
  new CompressionPlugin({
    algorithm: "gzip",                 // 指定压缩算法
    test: /\.js$|\.html$|\.css$/,
    threshold: 10 * 1024,         // 大于10kb则会被压缩
    minRatio: 0.8                         // 压缩比
  })
]

完成之后我们打包构建这个项目,查看构建的代码如果有超过10kb的代码就会被压缩成.gz为后缀的压缩包。

这样将项目放到服务器上之后访问项目时如果哪个文件有gz的压缩包就会优先加载gz文件下载到本地再解压这样就能提升加载速度。

优化方案之切割代码

组件同步引入和异步引入

同步和异步的概念

  • 同步:没有你就进行不下去,阻塞了整个线程。
  • 异步:没有你也可以进行下去,只有需要你的时候才找你,非阻塞。

VueRouter 中组件的引入方式也有同步和异步之分:

import Vue from 'vue'
import VueRouter from 'vue-router'

import Index from '../views/Index.vue'  // 同步引入

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Index',
    component: Index
  },
  {
    path: '/test',
    name: 'Test',
    component: () => {   // 异步引入
      return import('../components/Test.vue')
    }
  }
]

const router = new VueRouter({
  routes
})

export default router

对于 webpack,异步引入的代码依赖会在webpack工作的时候,自动被识别为切点,webpack 并不会将它合并到核心的 js 代码中,也就是合并到 main.bundle.js 中。

  • 异步引入的组件会单独打包成一个 js 文件。

浏览器打开项目时也不会立刻下载并运行该文件,在什么时候访问这个路由组件什么时候他就会下载并运行它。

异步引入文件合并打包

多个页面的时候才用同步引用,会全部引入 main.js 使得文件很大,浏览器下载 main.bundle.js 耗时太长,首屏访问缓慢,所以要将部分组件从 main 中分出去,这就是优化的方向。

但这又带来新的问题,一个组件独立成一个 js 文件,势必造成大量碎片化的 js 文件,这些 js 文件又比较小。虽小但都要浏览器发送一个请求去获取,造成大量的请求在获取这些碎片文件。给服务器造成压力。另一方面,这些 js 文件之间可能大小差距过大,这就会造成请求时间不稳定,一个页面快一个页面慢。

解决的办法和精灵图的思想一致,将这些碎片 js 文件进行合并打包,且打包成大小差不多的 js 文件。

异步引入组件利用 webpack 的魔法注释 /* webpackChunkName */ 进行合并打包:

  • 注意:包名注释要和路径放在一起
    const routes = [
    {
      path: '/page1',
      name: 'Page1',
      component: () => {
        return import(
           /*webpackChunkName:"page"*/
          '../components/Page1.vue'
        )
      }
    },
    {
      path: '/page2',
      name: 'Page2',
      component: () => {   
        return import(
          /*webpackChunkName:"page"*/
          '../components/Page2.vue'
        )
      }
    }
    ]
    
    块包到达浏览器后,浏览器能从这个块包中按需获取需要的资源。

访问到这个路由的才去异步加载所需 js ,和首次就整体加载来比,那不是一样的吗,反正下载这个js 的时间都是一样的。并不是,因为首次加载还要处理 css html 等其他的东西,而异步就不用了,只下载 js ,所以快很多。

vue react 的HTML都是通过 js 渲染到页面的。

优化方案之预获取

webpack文档:预获取/预加载模块(prefetch/preload module)

preload 与prefetch 的区别

  • prefetch(预获取):将来某些导航下可能需要的资源
  • preload(预加载):当前导航下可能需要资源

我们使用 prefetch 来优化。因为 preload 会占用本页资源加载时间,就开始加载其他页面。

优先加载本页的资源,空闲再去加载其他页面。现在访问首页的时候不会主动去下载其他页面的代码,但是通常会浏览一下首页,当停留在首页的时候我们就可以让浏览器利用这个空闲时间去下载其他页面的代码。

同样使用魔法注释 /* webpackPrefetch: true */实现。

const routes = [
  {
    path: '/page1',
    name: 'Page1',
    component: () => {
      return import(
        /*webpackChunkName:"page"*/
        /* webpackPrefetch: true */
        '../components/Page1.vue'
      )
    }
  },
  {
    path: '/page2',
    name: 'Page2',
    component: () => {   
      return import(
        /*webpackChunkName:"page"*/
        '../components/Page2.vue'
      )
    }
  }
]

image.png

优化手段依赖图谱

依赖图谱插件webpack-bundle-analyzer 图形化模块大小,直观的查看哪些包很大,提供优化的方向。

npm install --save-dev webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
}

默认会启用 8888 端口查看图谱
image.png
大部分文件会被打包进入 main.bundle.js,而它内部的依赖图谱确无法查看。
这里涉及公共依赖抽取,webpack 本身实现了公共依赖提取,通过参数配置
image.png

vendor 只要是从node_moudel 中引入的第三方的依赖都提取到 vendor
common 是自己个人封装的依赖,比如公共的组件,会被提取到 common
image.png

手动实现webpack启动器 webpack-cli

webpack-cli 是怎么启动 webpack 服务的?

平时启动 webpack 服务,npm run 脚本名,实际执行的是脚本对应的命令 vue-cli-service 后面对应的 js 启动文件。启动包的 package.json 文件中有一个 bin 对象,里面指明了 vue-cli-service 命令对应的启动文件。这个启动文件就包含了启动 webpack 服务的代码。

在 bin 对象中声明命令后,这个命令就会在这个包中暴露出来,运行这个命令就会调用这个 js 文件里的代码。当全局安装了这个启动包,那这个命令就会暴露在全局命令行控制台下。如果是 -S 那就只暴露在package.json 这个包环境下。
image.png
官方的 webpack-cli:
image.png

手动实现自己的 cli

我们可以把配置好了的 package.json 文件和 真正的 js 驱动文件一起打包,npm pack,这就相当于自己封装了一个脚手架,如果 js 里面写的是驱动 webpack 的代码,那这就是启动 webpack 的脚手架。别的项目引入这个依赖包,就相当于引入了webpack的脚手架。

本地引入:
image.png
用第三方的方式去启动 webpack ,官网有

image.png