前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >9102年:手写一个Vue的脚手架 【极致优化版】

9102年:手写一个Vue的脚手架 【极致优化版】

作者头像
Peter谭金杰
发布2019-08-02 14:14:58
8580
发布2019-08-02 14:14:58
举报

如果你对webpack不是很了解,请你关注我之前的文章,都是百星以上star的高质量文

  • 9102年:手写一个React完美版移动端脚手架
  • 前端性能优化不完全手册
  • GIT仓库地址
  • 欢迎你关注我的《前端进阶》专栏 一起突破学习
  • 文章内容都会不定期更新 记得一定要收藏
  • 本文书写于2019年5月17日 未经作者允许不得转载 使用最新版4.31版本webpack
  • webpack用了会上瘾,它也是突破你技术瓶颈的好方向,现在基本上任何东西都离不开webpack,webpack用得好,什么next nuxt随便上手(本人体会很深),本人参考了Vue脚手架,京东的webpack优化方案,以及本人的其他方面优化,着重在生产模式下的构建速度优化提升非常明显(当然开发环境下也是~),性能提升很明显哦~
  • 本配置完成功能:
  • 识别.Vue文件和template模板
  • tree shaking 摇树优化 删除掉无用代码
  • 引入babel polifill并且按需加载,识别一切代码
  • 识别 async / await 和 箭头函数
  • PWA功能,热刷新,安装后立即接管浏览器 离线后仍让可以访问网站 还可以在手机上添加网站到桌面使用
  • preload 预加载资源 prefetch按需请求资源 ,这里除了dns预解析外,建议其他的使用按需加载组件,顺便代码分割,这也是京东的优化方案
  • 配置nginx,拦截非预期请求(京东的方案)
  • CSS模块化,不怕命名冲突
  • 小图片的base64处理
  • 文件后缀省掉jsx js json
  • 实现VueRouter路由懒加载,按需加载 , 代码分割 指定多个路由同个chunkName并且打包到同个chunk中 实现代码精确分割
  • 支持less sass stylus等预处理
  • code spliting 优化首屏加载时间 不让一个文件体积过大
  • 提取公共代码,打包成一个chunk
  • 每个chunk有对应的chunkhash,每个文件有对应的contenthash,方便浏览器区别缓存
  • 图片压缩
  • CSS压缩
  • 增加CSS前缀 兼容各种浏览器
  • 对于各种不同文件打包输出指定文件夹下
  • 缓存babel的编译结果,加快编译速度
  • 每个入口文件,对应一个chunk,打包出来后对应一个文件 也是code spliting
  • 删除HTML文件的注释等无用内容
  • 每次编译删除旧的打包代码
  • CSS文件单独抽取出来
  • 让babel不仅缓存编译结果,还在第一次编译后开启多线程编译,极大加快构建速度

性能优化没有尽头,本人仅表达自己目前掌握的知识点,士别三日,刮目相看:每隔三天,技术就会进步一次

正式开始吧,假设你已经懂什么是entry output loader plugin ,如果不懂,看我上面的文章哦~

webpack常见配置

代码语言:javascript
复制
// 入口文件
  entry: {
    app: './src/js/index.js',
  },
  // 输出文件
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
    publicPath: '/'     //确保文件资源能够在 http://localhost:3000 下正确访问
  },
  // 开发者工具 source-map
  devtool: 'inline-source-map',
  // 创建开发者服务器
  devServer: {
    contentBase: './dist',
    hot: true                // 热更新
  },
  plugins: [
    // 删除dist目录
    new CleanWebpackPlugin(['dist']),
    // 重新穿件html文件
    new HtmlWebpackPlugin({
      title: 'Output Management'
    }),
    // 以便更容易查看要修补(patch)的依赖
    new webpack.NamedModulesPlugin(),
    // 热更新模块
    new webpack.HotModuleReplacementPlugin()
  ],
  // 环境
  mode: "development",
  // loader配置
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: [
          'file-loader'
        ]
      }
    ]
  }

这里面我们重点关注 moduleplugins属性,因为今天的重点是编写loaderplugin,需要配置这两个属性。

  • webpack 启动后,在读取配置的过程中会先执行 new MyPlugin(options) 初始化一个 MyPlugin获得其实例。在初始化 compiler 对象后,再调用 myPlugin.apply(compiler) 给插件实例传入 compiler 对象。

插件实例在获取到 compiler 对象后,就可以通过 compiler.plugin(事件名称, 回调函数) 监听到 Webpack 广播出来的事件。 并且可以通过 compiler 对象去操作 webpack。

  • Compiler 对象包含了 Webpack 环境所有的的配置信息,包含 options,loaders,plugins 这些信息,这个对象在 Webpack 启动时候被实例化,它是全局唯一的,可以简单地把它理解为 Webpack 实例;
    • Compilation象包含了当前的模块资源、编译生成资源、变化的文件等。当 Webpack 以开发模式运行时,每当检测到一个文件变化,一次新的 Compilation 将被创建。Compilation 对象也提供了很多事件回调供插件做扩展。通过 Compilation 也能读取到 Compiler` 对象。
    • Compiler 和 Compilation 的区别在于:
    • Compiler 代表了整个 Webpack 从启动到关闭的生命周期,而 Compilation 只是代表了一次新的编译。
    • 事件流
    • webpack 通过 Tapable来组织这条复杂的生产线。
    • webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。
    • webpack 的事件流机制应用了观察者模式,和 Node.js 中的 EventEmitter 非常相似。

1.2 打包原理

  • 识别入口文件
  • 通过逐层识别模块依赖。(Commonjs、amd或者es6import,webpack都会对其进行分析。来获取代码的依赖)
  • webpack做的就是分析代码。转换代码,编译代码,输出代码
  • 最终形成打包后的代码
  • 这些都是webpack的一些基础知识,对于理解webpack的工作机制很有帮助。

脚手架一般都是遵循了commonjs模块化方案,如果你不是很懂,那么看起来很费劲,我写的脚手架,就不使用模块化方案了,简单

  • 开始开发环境配置
  • 包管理器 使用yarn 不解释 就用yarn
  • 配置webpack.dev.js开发模式下的配置
  • yarn init -y
  • yarn add webpack webpack-cli (yarn会自动添加依赖是线上依赖还是开发环境的依赖)
配置入口
代码语言:javascript
复制
entry: path.resolve(__dirname, '../src/main.js')}
配置输出目录
代码语言:javascript
复制
output: {
        filename: 'js/[name].[hash:5].js',
        path: path.resolve(__dirname, '../dist'),
       
    },
引入Vue脚手架里基本配置的loader ,后面的loader都是往rules数组里加就行了~
代码语言:javascript
复制
module: {
        rules: [
            {
                test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
                use: [{
                        loader: 'url-loader',
                        options: {
                            limit: 10000,
                            name: 'img/[name]-[hash:5].[ext]',
                        }
                    }
                ]
            },
            {
                test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
                loader: 'url-loader',
                options: {
                    limit: 10000,
                    name: 'fonts/[name]-[hash:5].[ext]',
                }
            },
            {
                test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 4096,
                            name: 'media/[name]-[hash:5].[ext]',
                        }
                    }
                ]
            }
        ]
    },

有人会问 这么多我怎么看啊 别急 第一个url-loader是处理base64图片的,让低于limit大小的文件以base64形式使用,后面两个一样的套路,只是换了文件类型而已 ,不会的话,先复制过去跑一把?

配置识别.vue文件和tempalte模板 , yarn add vue vue-loader vue-template-compiler
代码语言:javascript
复制
加入loader

{
test:/\.vue$/,
loader:"vue-loader"
}

加入plugin 
const vueplugin = require('vue-loader/lib/plugin')


在webpack的plugin中

new  vueplugin()即可 
入口指定babel-polifill ,vendor代码分割公共模块,打包后这些代码都会在一个公共模块
代码语言:javascript
复制
 app: ['babel-polyfill', './src/index.js', './src/pages/home/index.js', './src/pages/home/categorys/index.jsx'],
 vendor: ['vuex', 'better-scroll', 'mint-ui', 'element-ui']
指定 html文件为模板打包输出,自动引入打包后的js文件
代码语言:javascript
复制
const HtmlWebpackPlugin = require('html-webpack-plugin');

plugins: [
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname,'../index.html'),
            filename: 'index.html'
        }),
    ]
省掉.vue的后缀 ,直接配置在module.exports对象中,跟entry同级
代码语言:javascript
复制
  resolve: {
        extensions: ['.js','.json','.vue'],
        
    }
加入识别html文件的loader
代码语言:javascript
复制
    {
    test: /\.(html)$/,
    loader: 'html-loader'
    }
开启多线程编译
代码语言:javascript
复制
const os = require('os')
    {
            loader: 'thread-loader',
            options: {
                workers: os.cpus().length   
                     }
    }
加入babel-loader 加入 babel-loader 还有 解析JSX ES6语法的 babel preset
代码语言:javascript
复制
  @babel/preset-env解析es6语法 
  @babel/plugin-syntax-dynamic-import解析vue的 import按需加载,附带code spliting功能
   
{
                            loader: 'babel-loader',
                            options: {   //jsx语法
                                presets: ["@babel/preset-react",
                                    //tree shaking 按需加载babel-polifill
                                    ["@babel/preset-env", { "modules": false, "useBuiltIns": "false", "corejs": 2 }]],
                                plugins: [
                                    //支持import 懒加载 
                                    "@babel/plugin-syntax-dynamic-import",
                                    //andt-mobile按需加载  true是less,如果不用less style的值可以写'css' 
                                    ["import", { libraryName: "antd-mobile", style: true }],
                                    //识别class组件
                                    ["@babel/plugin-proposal-class-properties", { "loose": true }],
                                ],
                                cacheDirectory: true
                            },
                        }
在使用上面的babel配置后 我们躺着就可以用vueRouter的路由懒加载了

路由懒加载

  • 当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
  • 结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。
  • 首先,可以将异步组件定义为返回一个 Promise 的工厂函数 (该函数返回的 Promise 应该 resolve 组件本身):
  • const Foo = () => Promise.resolve({ / 组件定义对象 / })
  • 第二,在 Webpack 中,我们可以使用动态 import语法来定义代码分块点 (split point):
    • import('./Foo.vue') // 返回 Promise

注意

  • 如果您使用的是 Babel,你将需要添加 syntax-dynamic-import 插件,才能使 Babel 可以正确地解析语法。
  • 结合这两者,这就是如何定义一个能够被 Webpack 自动代码分割的异步组件。
代码语言:javascript
复制
const Foo = () => import('./Foo.vue')

在路由配置中什么都不需要改变,只需要像往常一样使用 Foo:

const router = new VueRouter({
  routes: [
    { path: '/foo', component: Foo }
  ]
})
# 把组件按组分块

有时候我们想把某个路由下的所有组件都打包在同个异步块 (chunk) 中。只需要使用 命名 chunk,一个特殊的注释语法来提供 chunk name (需要 Webpack > 2.4)。
 
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')

Webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中。
加入插件 热更新plugin和html-webpack-plugin
代码语言:javascript
复制
   const HtmlWebpackPlugin = require('html-webpack-plugin')
   const webpack = require('webpack')
   new HtmlWebpackPlugin({
           template: './src/index.html'
       }),
   new webpack.HotModuleReplacementPlugin(),


    devServer: {
        contentBase: '../build',
        open: true,
        port: 5000,
        hot: true
    },
加入less-css识别的模块
代码语言:javascript
复制
 {
                    test: /\.(less|css)$/,
                    use: [
                        { loader: 'style-loader' },
                        {
                            loader: 'css-loader'
                            , options: {
                                modules: false, //不建议开启css模块化,某些ui组件库可能会按需加载失败
                                localIdentName: '[local]--[hash:base64:5]'
                            }
                        },
                        {
                            loader: 'less-loader',
                            options: { javascriptEnabled: true }
                        }
                    ]
                },

下面正式开始生产环境

踩坑是好事 为什么这次不放完整的源码 因为不去踩坑 永远提升不了技术

html杀掉无效的代码
代码语言:javascript
复制
 new HtmlWebpackPlugin({
        template: './src/index.html',
        minify: {
            removeComments: true,  
            collapseWhitespace: true,  
            removeRedundantAttributes: true,
            useShortDoctype: true, 
            removeEmptyAttributes: true,
            removeStyleLinkTypeAttributes: true,
            keepClosingSlash: true, 
            minifyJS: true,
            minifyCSS: true, 
            minifyURLs: true, 
         }
}),
加入图片压缩 性能优化很大
代码语言:javascript
复制
{
                test: /\.(jpg|jpeg|bmp|svg|png|webp|gif)$/,
                
                use:[
                    {loader: 'url-loader',
                    options: {
                        limit: 8 * 1024,
                        name: '[name].[hash:8].[ext]',
                        outputPath:'/img'
                    }},
                    {
                        loader: 'img-loader',
                        options: {
                          plugins: [
                            require('imagemin-gifsicle')({
                              interlaced: false
                            }),
                            require('imagemin-mozjpeg')({
                              progressive: true,
                              arithmetic: false
                            }),
                            require('imagemin-pngquant')({
                              floyd: 0.5,
                              speed: 2
                            }),
                            require('imagemin-svgo')({
                              plugins: [
                                { removeTitle: true },
                                { convertPathData: false }
                              ]
                            })
                          ]
                        }
                      }
                ]
                
                

            }
加入file-loader 把一些文件打包输出到固定的目录下
代码语言:javascript
复制
{
                exclude: /\.(js|json|less|css|jsx)$/,
                loader: 'file-loader',
                options: {
                    outputPath: 'media/',
                    name: '[name].[contenthash:8].[ext]'
                }
            }
加入压缩css的插件
代码语言:javascript
复制
    const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
    new OptimizeCssAssetsWebpackPlugin({
                cssProcessPluginOptions:{
                    preset:['default',{discardComments: {removeAll:true} }]
                }
            }),
加入code spliting代码分割 vue脚手架是同步异步分开割,我是直接一起割
代码语言:javascript
复制
    optimization: {
            runtimeChunk:true,  //设置为 true, 一个chunk打包后就是一个文件,一个chunk对应`一些js css 图片`等
            splitChunks: {
                chunks: 'all'  // 默认 entry 的 chunk 不会被拆分, 配置成 all, 就可以了拆分了,一个入口`JS`,
                //打包后就生成一个单独的文件
            }
        }
加入 WorkboxPlugin , PWA的插件
代码语言:javascript
复制
pwa这个技术其实要想真正用好,还是需要下点功夫,它有它的生命周期,以及它在浏览器中热更新带来的副作用等,需要认真研究。可以参考百度的lavas框架发展历史~
const WorkboxPlugin = require('workbox-webpack-plugin')


    new WorkboxPlugin.GenerateSW({ 
                clientsClaim: true, //让浏览器立即servece worker被接管
                skipWaiting: true,  // 更新sw文件后,立即插队到最前面 
                importWorkboxFrom: 'local',
                include: [/\.js$/, /\.css$/, /\.html$/,/\.jpg/,/\.jpeg/,/\.svg/,/\.webp/,/\.png/],
            }),
单页面应用的优化核心 :
  • 最重要的是路由懒加载 代码分割
  • 部分渲染在服务端完成 极大加快首屏渲染速度 VUE首选nuxt框架,也可以使用它的脚手架
  • 图片压缩和图片懒加载是对页面层次最大的优化之一
  • 后面继续书写next nuxtpwa的使用~
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 性能优化没有尽头,本人仅表达自己目前掌握的知识点,士别三日,刮目相看:每隔三天,技术就会进步一次
  • webpack常见配置
  • 1.2 打包原理
  • 脚手架一般都是遵循了commonjs模块化方案,如果你不是很懂,那么看起来很费劲,我写的脚手架,就不使用模块化方案了,简单粗暴
    • 配置入口
      • 配置输出目录
        • 引入Vue脚手架里基本配置的loader ,后面的loader都是往rules数组里加就行了~
          • 配置识别.vue文件和tempalte模板 , yarn add vue vue-loader vue-template-compiler
            • 入口指定babel-polifill ,vendor代码分割公共模块,打包后这些代码都会在一个公共模块
              • 指定 html文件为模板打包输出,自动引入打包后的js文件
                • 省掉.vue的后缀 ,直接配置在module.exports对象中,跟entry同级
                  • 加入识别html文件的loader
                    • 开启多线程编译
                      • 加入babel-loader 加入 babel-loader 还有 解析JSX ES6语法的 babel preset
                        • 在使用上面的babel配置后 我们躺着就可以用vueRouter的路由懒加载了
                        • 路由懒加载
                          • 加入插件 热更新plugin和html-webpack-plugin
                            • 加入less-css识别的模块
                            • 踩坑是好事 为什么这次不放完整的源码 因为不去踩坑 永远提升不了技术
                              • html杀掉无效的代码
                                • 加入图片压缩 性能优化很大
                                  • 加入file-loader 把一些文件打包输出到固定的目录下
                                    • 加入压缩css的插件
                                      • 加入code spliting代码分割 vue脚手架是同步异步分开割,我是直接一起割
                                        • 加入 WorkboxPlugin , PWA的插件
                                          • 单页面应用的优化核心 :
                                          相关产品与服务
                                          图片处理
                                          图片处理(Image Processing,IP)是由腾讯云数据万象提供的丰富的图片处理服务,广泛应用于腾讯内部各产品。支持对腾讯云对象存储 COS 或第三方源的图片进行处理,提供基础处理能力(图片裁剪、转格式、缩放、打水印等)、图片瘦身能力(Guetzli 压缩、AVIF 转码压缩)、盲水印版权保护能力,同时支持先进的图像 AI 功能(图像增强、图像标签、图像评分、图像修复、商品抠图等),满足多种业务场景下的图片处理需求。
                                          领券
                                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档