为什么要手写webpack 不用cli (脑子有病)并不是 其实是为了加深我们对webpack 的了解方便以后灵活运用webpack 的技术
"scripts": {"test": "echo \"Error: no test specified\" && exit 1","dev": "webpack-dev-server","build": "webpack"},
npm add webpack
npm add webpack -li // 注意 安装 3以上的版本
npm add webpack-dev-server // 启动dev 的环境
npm add html-webpack-plugin // webpack html模板插件
const { Configuration } = require('webpack') // 本来 没有智能提示的 不过可以通过注解的形式,就可以有智能提示了
const path = require('path') // 引入nodejs 的path
const htmlWebpackPlugin = require('html-webpack-plugin') // 引入 html 模板插件 配置模板const { CleanWebpackPlugin } = require('clean-webpack-plugin') // 清空dist文件使用的插件
const { VueLoaderPlugin } = require('vue-loader/dist/index'); // 这个插件是 为了 webpack 能够去识别 template 组件的const FriendlyErrorsWebpackPlugin = require("friendly-errors-webpack-plugin"); // 美化打包样式的/*** @type {Configuration}*/
const config = { // 本来这里是没有智能提示的mode: "development", // 指定 打包模式 开发环境打包出来的代码是不会压缩的 如果需要压缩的 需要用生产模式 productionentry: "./src/main.ts", // 入口文件 注意 如果是TS写的 就是 main.tsmodule: {rules: [ // 放在 rules 中 都是处理文件的{test: /\.vue$/, //解析vue 模板use: "vue-loader"}]},output: { // 出口filename: '[hash].js', // 打完包后的文件名path: path.resolve(__dirname, 'dist') // 拼接一下 dirname 输出位置},resolve: {alias: {"@": path.resolve(__dirname, './src') // 别名},extensions: ['.js', '.json', '.vue', '.ts', '.tsx'] //识别后缀},stats:"errors-only", //取消提示 devServer: { // 一些端口 跨域之类的配置proxy: {},port: 9001,hot: true,open: true,},externals: { // 性能优化的vue: "Vue" //CDN 引入},plugins: [ // 放在 plugins 中 都是插件new htmlWebpackPlugin({template: "./public/index.html" // 指定一下模板的位置}),new CleanWebpackPlugin(), //打包清空distnew VueLoaderPlugin(), //解析vuenew FriendlyErrorsWebpackPlugin({compilationSuccessInfo:{ //美化样式messages:['You application is running here http://localhost:8080']} })]
}module.exports = config
import { createApp } from 'vue';
import App from './App.vue' // 如果 ts 无法识别 vue文件 需要去声明一下 在 src 下面 创建一个 env.d.ts 的文件createApp(App).mount('#app') // 挂载点 由于是手写的 所以需要去 pubilc文件下的html文件的body中创建一个 div 给个 id 叫app 作为挂载点 到时候会渲染到这个地方
// src 下 env.d.ts 声明文件
declare module "*.vue" {import { DefineComponent } from "vue"const component: DefineComponent<{}, {}, any>export default component
}
npm add vue-loader@nextnpm add @vue/compiler-sfc
resolve: {alias: {"@": path.resolve(__dirname, './src') // 别名},extensions: ['.js', '.json', '.vue', '.ts', '.tsx'] //识别后缀},
npm add css-loadernpm add style-loader{test: /\.less$/, //解析 lessuse: ["style-loader", "css-loader", "less-loader"],},{test: /\.css$/, //解析cssuse: ["style-loader", "css-loader"],},
npm add typescript
npm add ts-loader{test: /\.ts$/, //解析tsloader: "ts-loader",options: {configFile: path.resolve(process.cwd(), 'tsconfig.json'),appendTsSuffixTo: [/\.vue$/]},}
npm add friendly-errors-webpack-plugin// 然后去 wabpackconfig 中配置const FriendlyErrorsWebpackPlugin = require("friendly-errors-webpack-plugin");new FriendlyErrorsWebpackPlugin({compilationSuccessInfo:{ //美化样式messages:['You application is running here http://localhost:8080'] // 这个是自己写的 包括端口}})
stats:"errors-only", //取消提示
devServer: {proxy: {}, // 跨域的 apiport: 8080, // 改端口hot: true, // 热更新open: true, // 打开网页},
externals: { // 性能优化的vue: "Vue" //CDN 引入 cdn 引入 需要添加一个scripit标签 的cdn引入地址},
我们可以使用谷歌浏览器自带的 DevTools 进行性能分析 LightHouse
参数介绍
从Performance页的表现结果来看,并提供了很多的时间信息,我们来解释下这些选项代表的意思:
FCP (First Contentful Paint):首次内容绘制的时间,浏览器第一次绘制DOM相关的内容,也是用户第一次看到页面内容的时间。
Speed Index: 页面各个可见部分的显示平均时间,当我们的页面上存在轮播图或者需要从后端获取内容加载时,这个数据会被影响到。
LCP (Largest Contentful Paint):最大内容绘制时间,页面最大的元素绘制完成的时间。
TTI(Time to Interactive):从页面开始渲染到用户可以与页面进行交互的时间,内容必须渲染完毕,交互元素绑定的事件已经注册完成。
TBT(Total Blocking Time):记录了首次内容绘制到用户可交互之间的时间,这段时间内,主进程被阻塞,会阻碍用户的交互,页面点击无反应。
CLS(Cumulative Layout Shift):计算布局偏移值得分,会比较两次渲染帧的内容偏移情况,可能导致用户想点击A按钮,但下一帧中,A按钮被挤到旁边,导致用户实际点击了B按钮。
由于我们使用的是vite vite打包是基于rollup 的我们可以使用 rollup 的插件
npm install rollup-plugin-visualizer
vite.config.ts 配置 记得设置open 不然无效
import { visualizer } from 'rollup-plugin-visualizer'; // 引入plugins: [vue(), vueJsx(),visualizer({ // 这里添加 然后还要设置openopen:true})],
最后进行 npm run build 打包
最后去查看 比如发现 某个ui库很大 可以使用按需导入之类的
也是 vite.config.js 里面
build:{ // 具体的要深入了解 这只是一部分chunkSizeWarningLimit:2000, cssCodeSplit:true, //css 拆分sourcemap:false, //不生成sourcemapminify:false, //是否禁用最小化混淆,esbuild打包速度最快,terser打包体积最小。assetsInlineLimit:5000 //小于该值 图片将打包成Base64
},
npm install vite-plugin-pwa -D
// 安装 导入
import { VitePWA } from 'vite-plugin-pwa'
plugins: [vue(),VitePWA(), vueJsx(),visualizer({open:true
})],
PWA 技术的出现就是让web网页无限接近于Native 应用
VitePWA({ // 这里也是一些基本的 需要深入了解workbox:{cacheId:"XIaoman",//缓存名称runtimeCaching:[{urlPattern:/.*\.js.*/, //缓存文件handler:"StaleWhileRevalidate", //重新验证时失效options:{cacheName:"XiaoMan-js", //缓存js,名称expiration:{maxEntries:30, //缓存文件数量 LRU算法maxAgeSeconds:30 * 24 * 60 * 60 //缓存有效期}}}]},})
最后进行 npm run build 打包会生成 sw.js
然后进入浏览器 f12 可以去应用模块(Application中的 service Workers)查看
1. 图片懒加载
import lazyPlugin from ‘vue3-lazy’
< img v-lazy="user.avatar" > // 这个插件可以使用 以自定义指令的形式使用懒加载
2. 虚拟列表 (这个 饿了吗 plus 里面已经提供了这个组件)
3. 多线程 使用 new Worker 创建 (VueUse 库已经集成了 webWorker)
worker脚本与主进程的脚本必须遵守同源限制。他们所在的路径协议、域名、端口号三者需要相同
const myWorker1 = new Worker("./calcBox.js");
都使用postMessage发送消息
worker.postMessage(arrayBuffer, [arrayBuffer]);
都使用onmessage接收消息
self.onmessage = function (e) {// xxx这里是worker脚本的内容
};
关闭
worker.terminate();
4. 防抖节流
同样VueUse 也是集成了
Vue3 Web Components
Web Components 提供了基于原生支持的、对视图层的封装能力,可以让单个组件相关的 javaScript、css、html模板运行在以html标签为界限的局部环境中,不会影响到全局,组件间也不会相互影响 。 再简单来说:就是提供了我们自定义标签的能力,并且提供了标签内完整的生命周期 。
Custom elements(自定义元素):JavaScript API,允许定义custom elements及其行为,然后可以在我们的用户界面中按照需要使用它们。
Shadow DOM(影子DOM):JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,开发者可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
HTML templates(HTML模板):和元素使开发者可以编写与HTML结构类似的组件和样式。然后它们可以作为自定义元素结构的基础被多次重用。
class Btn extends HTMLElement {constructor () {//调用super 来建立正确的原型链继承关系super()const p = this.h('p')p.innerText = '小满'p.setAttribute('style','height:200px;width:200px;border:1px solid #ccc;background:yellow')//表示 shadow DOM 子树的根节点。const shaDow = this.attachShadow({mode:"open"}) // 这里是重点之一shaDow.appendChild(this.p) // 这里 shaDow 一下}h (el) {return document.createElement(el)}/*** 生命周期*///当自定义元素第一次被连接到文档 DOM 时被调用。connectedCallback () {console.log('我已经插入了!!!嗷呜')}//当自定义元素与文档 DOM 断开连接时被调用。disconnectedCallback () {console.log('我已经断开了!!!嗷呜')}//当自定义元素被移动到新文档时被调用adoptedCallback () {console.log('我被移动了!!!嗷呜')}//当自定义元素的一个属性被增加、移除或更改时被调用attributeChangedCallback () {console.log('我被改变了!!!嗷呜')}}window.customElements.define('xiao-man',Btn) // 这里暴露出去
// 这个是 template 模式
class Btn extends HTMLElement {constructor() {//调用super 来建立正确的原型链继承关系super()const template = this.h('template')template.innerHTML = `小满`//表示 shadow DOM 子树的根节点。const shaDow = this.attachShadow({ mode: "open" })shaDow.appendChild(template.content.cloneNode(true))}h(el) {return document.createElement(el)}/*** 生命周期*///当自定义元素第一次被连接到文档 DOM 时被调用。connectedCallback() {console.log('我已经插入了!!!嗷呜')}//当自定义元素与文档 DOM 断开连接时被调用。disconnectedCallback() {console.log('我已经断开了!!!嗷呜')}//当自定义元素被移动到新文档时被调用adoptedCallback() {console.log('我被移动了!!!嗷呜')}//当自定义元素的一个属性被增加、移除或更改时被调用attributeChangedCallback() {console.log('我被改变了!!!嗷呜')}}window.customElements.define('xiao-man', Btn)
web Component // 导入
// 使用
defineCustomElement
1. 首先配置 vite.config.js
/*vite config ts 配置*/
vue({template:{compilerOptions:{isCustomElement:(tag)=> tag.includes('wamgsihua-') // 这个 名字 代表组件名设置为 前面必须带有这个开头的}}
})
2. 父组件
// 这里传递参数 如果是对象需要序列化 他是作用于 标签上的 必须要 JSON.stringify 这个写法 转一下
3. 子组件(custom-vue.ce.vue 文件名结尾 必须要 .ce.vue)
1111 {{title}}
Vue2 使用的是 Object.defineProperty Vue3 使用的是 Proxy
对象只能劫持 设置好的数据,新增的数据需要Vue.Set(xxx) 数组只能操作七种方法,修改某一项值无法劫持等。
Vue3 的响应式原理依赖了 Proxy 这个核心 API,通过 Proxy 可以劫持对象的某些操作。
export const reactive = (target:T) => {return new Proxy(target,{ // 用的 Proxyget (target,key,receiver) {const res = Reflect.get(target,key,receiver) as object// 这不 不直接用 target[key] 怕某些特定情况下上下文错乱// Reflect 这个是 es6新增的 具体的要去看一下文档return res},set (target,key,value,receiver) { // 这里多了一个 valueconst res = Reflect.set(target,key,value,receiver)// 这里 Reflect 第二个作用 正好返回一个 boolean 而set需要 booleanreturn res}})
}
1. 实现effect 副作用函数
副作用函数,顾名思义,会产生副作用的函数被称为副作用函数。那么什么是副作用呢?如果几个函数的运行,可能会影响到其他函数或变量,那么这种影响就是一种副作用(副作用函数不一定非要对某些函数产生副作用,如果一个函数修改了全局变量,这其实也是个副作用函数。)
let activeEffect; // 定义一个全局变量 把闭包收集起来
export const effect = (fn:Function) => {// 闭包const _effect = function () {activeEffect = _effect;fn()}_effect()
}
使用一个全局变量 active 收集当前副作用函数,并且初始化的时候调用一下
2. 实现track // 收集
const targetMap = new WeakMap()
export const track = (target,key) =>{let depsMap = targetMap.get(target)if(!depsMap){depsMap = new Map()targetMap.set(target,depsMap)}let deps = depsMap.get(key)if(!deps){deps = new Set()depsMap.set(key,deps)}deps.add(activeEffect) // 收集起来
}
3. 实现trigger // 更新
export const trigger = (target,key) => {const depsMap = targetMap.get(target)const deps = depsMap.get(key)deps.forEach(effect=>effect()) // 使用 副作用函数
}
4. 当我们进行赋值的时候会调用 set 然后 触发收集的副作用函数
import {track,trigger} from './effect' // 引用这两个export const reactive = (target:T) => {return new Proxy(target,{get (target,key,receiver) {const res = Reflect.get(target,key,receiver) as objecttrack(target,key)return res},set (target,key,value,receiver) {const res = Reflect.set(target,key,value,receiver)trigger(target,key)return res}})
}
5. 给 reactive 添加这两个方法
Document
import { track, trigger } from './effect'const isObject = (target) => target != null && typeof target == 'object' // 如果是 对象类型的数据 需要改造一下export const reactive = (target: T) => {return new Proxy(target, {get(target, key, receiver) {const res = Reflect.get(target, key, receiver) as objecttrack(target, key)if (isObject(res)) { // 这里要做一个深层次的递归 监听深层次的return reactive(res)}return res},set(target, key, value, receiver) {const res = Reflect.set(target, key, value, receiver)trigger(target, key)return res}})
}
Document
上一篇:高新技术企业的认定条件(三)