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

    xiaoxiao2022-07-12  153

    使用最新版4.31版本webpackwebpack用了会上瘾,它也是突破你技术瓶颈的好方向,现在基本上任何东西都离不开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常见配置

    // 入口文件 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' ] } ] }

    这里面我们重点关注 module和plugins属性,因为今天的重点是编写loader和plugin,需要配置这两个属性。

    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或者es6的import,webpack都会对其进行分析。来获取代码的依赖)webpack做的就是分析代码。转换代码,编译代码,输出代码最终形成打包后的代码这些都是webpack的一些基础知识,对于理解webpack的工作机制很有帮助。

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

    开始开发环境配置包管理器 使用yarn 不解释 就用yarn配置webpack.dev.js开发模式下的配置yarn init -yyarn add webpack webpack-cli (yarn会自动添加依赖是线上依赖还是开发环境的依赖)

    配置入口

    entry: path.resolve(__dirname, '../src/main.js')}

    配置输出目录

    output: { filename: 'js/[name].[hash:5].js', path: path.resolve(__dirname, '../dist'), },

    引入Vue脚手架里基本配置的loader ,后面的loader都是往rules数组里加就行了

    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

    加入loader { test:/\.vue$/, loader:"vue-loader" } 加入plugin const vueplugin = require('vue-loader/lib/plugin') 在webpack的plugin中 new vueplugin()即可

    入口指定babel-polifill ,vendor代码分割公共模块,打包后这些代码都会在一个公共模块

    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文件

    const HtmlWebpackPlugin = require('html-webpack-plugin'); plugins: [ new HtmlWebpackPlugin({ template: path.resolve(__dirname,'../index.html'), filename: 'index.html' }), ]

    省掉.vue的后缀 ,直接配置在module.exports对象中,跟entry同级

    resolve: { extensions: ['.js','.json','.vue'], }

    加入识别html文件的loader

    { test: /\.(html)$/, loader: 'html-loader' }

    开启多线程编译

    const os = require('os') { loader: 'thread-loader', options: { workers: os.cpus().length } }

    加入babel-loader 加入 babel-loader 还有 解析JSX ES6语法的 babel preset

    @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 自动代码分割的异步组件。 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

    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识别的模块

    { 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杀掉无效的代码

    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, } }),

    加入图片压缩 性能优化很大

    { 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 把一些文件打包输出到固定的目录下

    { exclude: /\.(js|json|less|css|jsx)$/, loader: 'file-loader', options: { outputPath: 'media/', name: '[name].[contenthash:8].[ext]' } }

    加入压缩css的插件

    const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin') new OptimizeCssAssetsWebpackPlugin({ cssProcessPluginOptions:{ preset:['default',{discardComments: {removeAll:true} }] } }),

    加入code spliting代码分割 vue脚手架是同步异步分开割,我是直接一起割

    optimization: { runtimeChunk:true, //设置为 true, 一个chunk打包后就是一个文件,一个chunk对应`一些js css 图片`等 splitChunks: { chunks: 'all' // 默认 entry 的 chunk 不会被拆分, 配置成 all, 就可以了拆分了,一个入口`JS`, //打包后就生成一个单独的文件 } }

    加入 WorkboxPlugin , PWA的插件

    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 nuxt和pwa的使用~

    脚手架的搭建过程很多坑,但是却能大大提升你的技术天花板

     

    福利,各种教程资源,应有尽有

    《Java基础、入门、精通、架构师全套资源》

    最新回复(0)