面试篇(二)

你在项目里是怎么封装ajax的

使用的axios库,用拦截器进行request和response的处理,在request拦截器加入token,header,根据request,url,method,body生成canToken放在全局,请求之前检查全局canelToken是否包含,如果已经存在,调用cancel,在response里removeCancelToken,处理401 auth, 404 not found, 500 server error, 其它的抛出去,在调用的时候自己处理,show toast or others。

原型链怎么使用的,js如何使用原型链实现继承,封装和多态

正常开发很少用到原型链,比如react里我们给Component的class加了setState。我们在使用的时候比如 LoginComponent extends React.Component就可以使用setState了。

对requestIdelCallback和requestAnimationFrame的理解

大多数电脑显示器的刷新频率是60Hz,大概相当于每秒钟重绘60次。因此,最平滑动画的最佳循环间隔是1000ms/60,约等于16.6ms。

requestAnimationFrame的运行机制:(在每次渲染前执行,每一帧都会执行)

var timer;
btn.onclick = function(){
    myDiv.style.width = '0';
    cancelAnimationFrame(timer);
    timer = requestAnimationFrame(function fn(){
        if(parseInt(myDiv.style.width) < 500){
            myDiv.style.width = parseInt(myDiv.style.width) + 5 + 'px';
            myDiv.innerHTML =     parseInt(myDiv.style.width)/5 + '%';
            timer = requestAnimationFrame(fn);
        }else{
            cancelAnimationFrame(timer);
        }    
    });
}

requesetIdleCallback每一帧有空余时间时会被触发,是一个属于宏任务的回调,就像setTimeout一样。不同的是,setTimeout的执行时机由我们传入的回调时间去控制,requesetIdleCallback是受屏幕的刷新率去控制。假如浏览器一直处于非常忙碌的状态,requestIdleCallback 注册的任务有可能永远不会执行,此时可通过设置 timeout来保证执行。

因为requestIdCallback发生在一帧的最后,此时页面布局已经完成,所以不建议在 requestIdleCallback 里再操作 DOM,这样会导致页面再次重绘。

用法如下:React的Fiber树在执行过程中采用了此模式。

requestIdleCallback(myNonEssentialWork, { timeout: 2000 });
​
// 任务队列
const tasks = [
 () => {
   // some operation
   console.log("第一个任务");
 },
 () => {
   // some operation
   console.log("第二个任务");
 },
 () => {
   // some operation
   console.log("第三个任务");
 },
];
​
function myNonEssentialWork (deadline) {
 // 如果帧内有富余的时间,或者超时
 while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && tasks.length > 0) {
   const currentWorkUnit = tasks.shift()();
   // do task
 }
​
 if (tasks.length > 0)
   requestIdleCallback(myNonEssentialWork);
 }
​

什么是闭包:

①要理解闭包,首先理解javascript特殊的变量作用域,变量的作用于无非就是两种:全局变量,局部变量。
②javascript语言的特殊处就是函数内部可以读取全局变量。
③我们有时候需要得到函数内的局部变量,但是在正常情况下,这是不能读取到的,这时候就需要用到闭包。在javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。闭包是指有权访问另一个函数作用域中的变量的函数。其本质是函数的作用域链中保存着外部函数变量对象的引用。

二.闭包的应用场景:
①函数作为参数被传递
②函数作为返回值被返回
③实际应用(隐藏数据):为什么说隐藏数据了呢,因为普通用户只能通过get set等api对数据进行查看和更改等操作,没法对data直接更改,达到所谓隐藏数据的效果;
封装功能时(需要使用私有的属性和方法),函数防抖、函数节流
单例模式

三.闭包的优点:
(一)一个是前面提到的可以读取函数内部的变量
(二)另一个就是可以重复使用变量,并且不会造成变量污染
①全局变量可以重复使用,但是容易造成变量污染。不同的地方定义了相同的全局变量,这样就会产生混乱。”
②局部变量仅在局部作用域内有效,不可以重复使用,不会造成变量污染。
③闭包结合了全局变量和局部变量的优点。可以重复使用变量,并且不会造成变量污染

四.闭包的缺点:
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

闭包对象第一次调用的时候,会在栈里面开辟空间,并返回引用指针,只有当这个指针不再被访问的时候才会被释放。而我们之后会使用这个指针去继续操作这个堆空间,所以这个空间是一直可被访问到的。而js采用可访问,可达算法进行的垃圾回收,所以此空间的数据是不会被GC回收。

你对EventSource和轮询及websocket的理解

EventSource

  • EventSource(Server-sent events)简称SSE用于向服务端发送事件,它是基于http协议的单向通讯技术,以text/event-stream格式接受事件,如果不关闭会一直处于连接状态,直到调用EventSource.close()方法才能关闭连接;
  • 是服务器->客户端的,所以它不能处理客户端请求流
  • 明确指定用于传输UTF-8数据的,所以对于传输二进制流是低效率的,即使你转为base64的话,反而增加带宽的负载,得不偿失。

轮询

  •  是一种简单粗暴,同样也是一种效率低下的实现“实时”通讯方案,这种方案的原理就是定期向服务器发送请求,主动拉取最新的消息队列
  • 比如轮询的间隔小于服务器信息跟新频率,会浪费很多HTTP请求,消耗宝贵的CPU时间和带宽。容易导致请求轰炸
  • 长轮询的是浏览器发送一个请求到服务器,服务器只有在有可用的新数据时才会响应,长轮询和短轮询比起来,明显减少了很多不必要的http请求次数,相比之下节约了资源

websocket

  • 双向通信,实时性高,相对前两者性能最佳

你对React Fiber的设计有什么看法?

Fiber结构是react16引入的,是一种双向链表结构,异步可中断设计。在任何给定时间,ReactJS维护两个Virtual DOM,一个具有更新的状态Virtual DOM,另一个具有先前的状态Virtual DOM。

workInProgress Tree 保存当先更新中的进度快照,用于下一个时间片的断点恢复, 跟 Fiber Tree 的构成几乎一样, 在一次更新的开始时跟 Fiber Tree 是一样的.

在首次渲染的过程中,React 通过 react-dom 中提供的方法创建组件和与组件相应的 Fiber (Tree) ,此后就不会再生成新树,运行时永远维护这一棵树,调度和更新的计算完成后 Fiber Tree 会根据 effect 去实现更新。这两棵树构成了双缓冲树, 以 fiber tree 为主,workInProgress tree 为辅。

一次更新的操作都是在 workInProgress Tree 上完成的,当更新完成后再用 workInProgress Tree 替换掉原有的 Fiber Tree 

Fiber 总的来说可以分成两个部分,一个是调和过程(可中断),一个是提交过程(不可中断)

fiber 是解决性能问题的,而 hooks 是解决逻辑复用问题的

虚拟dom在Vue和React有什么不同?diff算法有什么不同,vue为什么叫渐进式?

Vue2的核心Diff算法采用了双端比较的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。相比React的Diff算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。

React是从左到右比较。

响应式原理:

Vue依赖收集,自动优化,数据可变。Vue递归监听data的所有属性,直接修改。当数据改变时,自动找到引用组件重新渲染。

React基于状态机,手动优化,数据不可变,需要setState驱动新的state替换老的state。当数据改变时,以组件为根目录,默认全部重新渲染, 所以 React 中会需要 shouldComponentUpdate 这个生命周期函数方法来进行控制

什么是热更新和热模块替换,如何设计?

HMR在配置成功以后,修改CSS/JS不会进行页面刷新。

HMR 需要用到 HotModuleReplacementPlugin 这个插件,这个插件是 webpack 自带的插件。

在 devServer 中配置 hot 为 true

css文件我们会配置css-loader,css-loader中已经增加了module.hot.accept的支持,所以即使不配置module.hot.accept,对于css也可以HMR,但是如果JS没有调用module.hot.accept,HMR执行找不到对应的内容,则会直接刷新页面,可以设置参数hotOnly: true来防止自动刷新。

原理:

在热更新开启后,当webpack打包时,会向client端注入一段HMR runtime代码,同时server端会启动了一个HMR服务器,然后通过websocket和注入的runtime进行通信。
在webpack检测到文件修改后,会重新构建,并通过ws向client端发送更新消息,浏览器通过jsonp拉取更新过的模块,回调触发模块热更新逻辑。

前端的router有什么区别,location hash和history?

hash路由的话,只是hash部分改变并不会刷新页面

history路由的话,是根据history.pushSate和history.replaceState这两个方法,并不会刷新路由。

路由实现:Hash模式,通过hashChange事件监听。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Hash 模式</title>
</head>
  <body>
    <div>
      <ul>
        <li><a href="#/page1">page1</a></li>
        <li><a href="#/page2">page2</a></li>
      </ul>
      <!--渲染对应组件的地方-->
      <div id="route-view"></div>
    </div>
  <script type="text/javascript">
    // 第一次加载的时候,不会执行 hashchange 监听事件,默认执行一次
    // DOMContentLoaded 为浏览器 DOM 加载完成时触发
    window.addEventListener('DOMContentLoaded', Load)
    window.addEventListener('hashchange', HashChange)
    // 展示页面组件的节点
    var routeView = null
    function Load() {
      routeView = document.getElementById('route-view')
      HashChange()
    }
    function HashChange() {
      // 每次触发 hashchange 事件,通过 location.hash 拿到当前浏览器地址的 hash 值
      // 根据不同的路径展示不同的内容
      switch(location.hash) {
      case '#/page1':
        routeView.innerHTML = 'page1'
        return
      case '#/page2':
        routeView.innerHTML = 'page2'
        return
      default:
        routeView.innerHTML = 'page1'
        return
      }
    }
  </script>
  </body>
</html>

用history实现前端路由:history.pushState(),和replaceState()都不会触发popstate事件。只有点击后退按钮或者手动调用history.back,forward才会触发。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>History 模式</title>
</head>
<body>
  <div>
    <ul>
      <li><a href="/page1">page1</a></li>
      <li><a href="/page2">page2</a></li>
    </ul>
    <div id="route-view"></div>
  </div>
  <script type="text/javascript">
    window.addEventListener('DOMContentLoaded', Load)
    window.addEventListener('popstate', PopChange)
    var routeView = null
    function Load() {
      routeView = document.getElementById('route-view')
      // 默认执行一次 popstate 的回调函数,匹配一次页面组件
      PopChange()
      // 获取所有带 href 属性的 a 标签节点
      var aList = document.querySelectorAll('a[href]')
      // 遍历 a 标签节点数组,阻止默认事件,添加点击事件回调函数
      aList.forEach(aNode => aNode.addEventListener('click', function(e) {
        e.preventDefault() //阻止a标签的默认事件
        var href = aNode.getAttribute('href')
        //  手动修改浏览器的地址栏
        history.pushState(null, '', href)
        // 通过 history.pushState 手动修改地址栏,
        // popstate 是监听不到地址栏的变化,所以此处需要手动执行回调函数 PopChange
        PopChange()
      }))
    }
    function PopChange() {
      console.log('location', location)
      switch(location.pathname) {
      case '/page1':
        routeView.innerHTML = 'page1'
        return
      case '/page2':
        routeView.innerHTML = 'page2'
        return
      default:
        routeView.innerHTML = 'page1'
        return
      }
    }
  </script>
</body>
</html>

浏览器渲染原理,重绘和重排/回流的区别,GC的原理?

  1. 每一轮 Event Loop 都会伴随着渲染吗?
  2. requestAnimationFrame 在哪个阶段执行,在渲染前还是后?在 microTask 的前还是后?
  3. requestIdleCallback 在哪个阶段执行?如何去执行?在渲染前还是后?在 microTask 的前还是后?
  4. resizescroll 这些事件是何时去派发的。
  1. 事件循环不一定每轮都伴随着重渲染,但是如果有微任务,一定会伴随着微任务执行
  2. 决定浏览器视图是否渲染的因素很多,浏览器是非常聪明的。
  3. requestAnimationFrame在重新渲染屏幕之前执行,非常适合用来做动画。
  4. requestIdleCallback在渲染屏幕之后执行,并且是否有空执行要看浏览器的调度,如果你一定要它在某个时间内执行,请使用 timeout参数。
  5. resizescroll事件其实自带节流,它只在 Event Loop 的渲染阶段去派发事件到 EventTarget 上。

 网页的生成过程,大致可以分成五步。

  1. HTML代码转化成DOM Tree
  2. CSS代码转化成CSSOM Tree(CSS Object Model)
  3. 结合DOM和CSSOM,生成一棵渲染树Render Tree
  4. 生成布局(flow),将所有渲染树进行平面合成(!此步骤再次触发即回流)
  5. 将布局绘制(paint)在屏幕上(显卡,此步骤再次触发即重绘)

网页生成的时候,至少会渲染一次。用户访问的过程中,还会不断重新渲染。这五步里面,第一步到第三步都非常快,耗时的是第四步和第五步。"生成布局"(flow)和"绘制"(paint)这两步,合称为"渲染"(render),重排/回流是一个概念。重排比重绘要耗时。

以下三种情况,会导致网页重新渲染。

  • 修改DOM
  • 修改样式表
  • 用户事件(比如鼠标悬停、页面滚动、输入框键入文字、改变窗口大小等等)

重新渲染,就需要重新生成布局和重新绘制。前者叫做"重排"(reflow),后者叫做"重绘"(repaint)。

"重绘"不一定需要"重排",比如改变某个网页元素的颜色,就只会触发"重绘",不会触发"重排",因为布局没有改变。

但是,"重排"必然导致"重绘",比如改变一个网页元素的位置,就会同时触发"重排"和"重绘",因为布局改变了。

  • css加载不会阻塞DOM树的解析
  • css加载会阻塞后面js语句的执行

为了防止css阻塞,引起页面白屏,可以提高页面加载速度

  • 使用cdn
  • 对css进行压缩
  • 合理利用缓存
  • 减少http请求,将多个css文件合并

垃圾回收是指:JavaScript代码运行时,需要分配内存空间来储存变量和值。当变量不在参与运行时,就需要系统收回被占用的内存空间。

 V8 采⽤的可访问性(reachability)算法来判断堆中的对象是否是活动对象。

webpack里loader和plugin是干啥用的,执行顺序是怎么样的?

loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中

在webpack运行的生命周期中会广播出许多事件,plugin可以监听这些事件,在合适的时机通过webpack提供的API改变输出结果。plugin是一个扩展器,它丰富了webpack本身,针对是loader结束后,webpack打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听webpack打包过程中的某些节点,执行广泛的任务 。

plugin通过tap注册compiler上的hook,使得webpack执行到指定时机执行回调函数。

什么是侵入式开发框架,你对装饰器的理解?

对fetch和axios的优缺点理解?

如何计算首屏加载时间?

首屏加载的时间主要是指页面的dom,脚本,样式都加载完毕了,这时候页面的脚本已经执行完毕页面的交互已经可以使用,样式也都完整,但是可能某些图片还在加载中。

时间点可以采用performance.timing,可以采用DOMContentLoaded +首屏中图片加载完时间(去除首屏不加载图片)

  1. DOMContentLoaded 事件,表示直接书写在HTML页面中的内容但不包括外部资源被加载完成的时间,其中外部资源指的是css、js、图片、flash等需要产生额外HTTP请求的内容。
  2. onload 事件,表示连同外部资源被加载完成的时间。
但是实际情况下问题会比较复杂,比如有些网站的主要内容是通过onload事件处理函数中的代码逻辑来处理加载,那么就得用代码来测量onload事件处理函数的运行时间。也有一些可能会以定时器的方式,或者通过XHR异步方式来加载,测量起来就更麻烦了。实现方式导致加载时机不确定,使加载时间的测量方式很难有统一。
<script type="text/javascript">
    window.logInfo = {};  //统计页面加载时间
    window.logInfo.openTime = performance.timing.navigationStart;
    window.logInfo.whiteScreenTime = +new Date() - window.logInfo.openTime;
    document.addEventListener('DOMContentLoaded',function (event) {
        window.logInfo.readyTime = +new Date() - window.logInfo.openTime;
    });
    window.onload = function () {
      window.logInfo.allloadTime = +new Date() - window.logInfo.openTime;
      var timname = {
            whiteScreenTime: '白屏时间',
            readyTime: '用户可操作时间',
            allloadTime: '总下载时间'
        };
        var logStr = '';
        for (var i in timname) {
            console.warn(timname[i] + ':' + window.logInfo[i] + 'ms');
            logStr += '&' + i + '=' + window.logInfo[i] + 'ms';
        }
      (new Image()).src = '/?action=speedlog' + logStr;
  };
</script>

对redux理解

redux包括三个部分: reducer, action, store

store: 唯一数据源,通过 store.subscribe可以订阅更新, 通过store.dispatch(action)可以发布更新,reducer是控制处理器。可以根据action的type,定制state的返回,reducer可以拆分。

import { createStore } from 'redux';
/**
 * 这是一个 reducer,形式为 (state, action) => state 的纯函数
 */
function reducer(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return state + 1;
  case 'DECREMENT':
    return state - 1;
  default:
    return state;
  }
}

const store = createStore(reducer);

// 可以手动订阅更新,也可以事件绑定到视图层。
store.subscribe(() =>
  console.log(store.getState())
);

// 改变内部 state 惟一方法是 dispatch 一个 action。
// action 可以被序列化,用日记记录和储存下来,后期还可以以回放的方式执行
store.dispatch({ type: 'INCREMENT' });
// 1
store.dispatch({ type: 'INCREMENT' });
// 2
store.dispatch({ type: 'DECREMENT' });
// 1

Redux 应用中数据的生命周期遵循下面 4 个步骤:

  1. 调用 store.dispatch(action)
  2. Redux store 调用传入的 reducer 函数。
  3. 根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树。
  4. Redux store 保存了根 reducer 返回的完整 state 树。

ReduxToolkit是redux的最佳实现,它简化了redux的流程。

import { createSlice, configureStore } from '@reduxjs/toolkit'

const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0
  },
  reducers: {
    incremented: state => {
      // Redux Toolkit allows us to write "mutating" logic in reducers. It
      // doesn't actually mutate the state because it uses the Immer library,
      // which detects changes to a "draft state" and produces a brand new
      // immutable state based off those changes
      state.value += 1
    },
    decremented: state => {
      state.value -= 1
    }
  }
})

export const { incremented, decremented } = counterSlice.actions

const store = configureStore({
  reducer: counterSlice.reducer
})

// Can still subscribe to the store
store.subscribe(() => console.log(store.getState()))

// Still pass action objects to `dispatch`, but they're created for us
store.dispatch(incremented())
// {value: 1}
store.dispatch(incremented())
// {value: 2}
store.dispatch(decremented())
// {value: 1}

前端工程化,组件化,模块化,微前端,前端工程化部署

前端工程化:比如咱们现在的前后端分离项目,可以单独作为一个项目运行,拥有自己的配置,项目结构,编译,打包等

组件化:比如我们经常用的重复的小功能,按钮,输入框,或者复杂的组合组件比如模态框, 表格等,我们把他重新封装成可复用的单元。这样当你想在不同页面使用,就可以减少很多不必要的重复代码。

模块化:现在的前端项目,可以认为一个文件就是一个模块,无论是js,css,img文件,都可以以模块的思维去解读他。JS模块化方案很多有AMD/CommonJS/UMD/ES6 Module等,CSS模块化开发大多是在less、sass、stylus等预处理器。

工程化构建:前端构建过程一般包括以下几个过程:

  • 代码检查
  • 运行单元测试等
  • 语言编译
  • 依赖分析、打包、替换等
  • 代码压缩、spirit 图片压缩等
  • 版本生成

微前端:微前端借鉴了微服务的架构理念,将一个庞大的前端应用拆分为多个独立灵活的小型应用,每个应用都可以独立开发、独立运行、独立部署,再将这些小型应用联合为一个完整的应用。微前端既可以将多个项目融合为一,又可以减少项目之间的耦合,提升项目扩展性,相比一整块的前端仓库,微前端架构下的前端仓库倾向于更小更灵活。

目前可用的微前端方案:

基于 iframe 完全隔离的方案

  1. JS、CSS 都是独立的运行环境,页面上可以放多个 iframe 来组合业务
  2. 无法保持路由状态,刷新后路由状态就丢失
  3. 完全的隔离导致与子应用的交互变得极其困难
  4. iframe 中的弹窗无法突破其本身
  5. 整个应用全量资源加载,加载太慢

基于 single-spa 路由劫持方案

single-spa本身是没有实现样式隔离。js执行隔离的。

  1. qiankun 孵化自蚂蚁金融科技基于微前端架构的云产品统一接入平台
  2. 通过 import-html-entry 包解析 HTML 获取资源路径,然后对资源进行解析、加载
  3. 通过对执行环境的修改,它实现了 JS 沙箱样式隔离 等特性

京东 micro-app 方案

  1. 借鉴了 WebComponent 的思想,通过 CustomElement 结合自定义的 ShadowDom,将微前端封装成一个类 webComponents 组件,从而实现微前端的组件化渲染。

style-loader 和 css-loader,sass-loader

css-loader 会对 @import 和 url() 进行处理,就像 js 解析 import/require() 一样,默认生成一个数组存放存放处理后的样式字符串,并将其导出

style-loader的作用是把 CSS 插入到 DOM 中,就是处理css-loader导出的模块数组,然后将样式通过style标签或者其他形式插入到DOM中。

sass-loader加载sass/scss, 并且把sass/scss编译成css。而该loader依赖于node-sass。而node-sass依赖于node,所以要注意node-sass和node之间的版本支持

postcss-loader更像一个工厂,支持插件组装,可以通过编写postcss的插件,比如autoprefixer(生成兼容性css)这样的加上更多功能

使用postcss-loader:

npm install --save-dev postcss-loader postcss autoprefixer
 module: {
    rules: [
      {
        test: /\.less$/,
        use: [ 'style-loader', 'css-loader', 'postcss-loader', 'sass-loader' ] // use 从后往前读取
      }
    ]
  }
{
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: [
                  [
                    'autoprefixer',
                    {
                      // 选项
                    },
                  ],
                ],
              },
            },

Webpack和rollup配置对比:

Webpack:

entry: 打包入口

output: 输出

module =>rules => loader: 模块解析

plugins: 插手打包过程

const path = require('path')
 
const config = {
    mode: 'development', //模式设置
    target: 'web',
    entry: './src/index.ts',  // 打包文件
    output: {
        filename: 'index.js', // 输出文件
        path: path.resolve(__dirname, 'lib'), // 输出路径
        libraryTarget: 'commonjs',
    },
    module: {
        rules: [
            {
                test: /\.ts$/,
                loader: 'ts-loader',
            },
            {
                test: /\.js$/, //用正则匹配文件,用require或者import引入的都会匹配到
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                }, //加载器名,就是上一步安装的loader
                exclude: /node_modules/, //排除node_modules目录,我们不加载node模块中的js哦~
                // include: /@gfe\/universal-logger/,
                include: [
                    path.resolve(__dirname,'./src'),
                    path.resolve(__dirname,'./node_modules/@gfe')
                ],
            },
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader',
                    // 'postcss-loader'
                ],
                //依次使用以上loader加载css文件,postcss-loader可以暂时不加,后面再深入修改webpack配置的时候再说用处
                //
                //也可以写成这样 loader:"style-loader!css-loader!postcss-loader"
            },
            {
                test: /\.(png|jpe?j|gif|svg)(\?.*)?$/,
                loader: 'url-loader',
                options: {
                    limit: 10000,
                    name: 'img/[name].[ext]?[hash]',
                },
                //图片文件大小小于limit的数值,就会被改写成base64直接填入url里面,
                //不然会输出到dist/img目录下,[name]原文件名,[ext]原后缀,[hash]在url上加上一点哈希值避免缓存。
            },
            {
                test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
                loader: 'url-loader',
                options: {
                    limit: 10000,
                    name: 'fonts/[name].[ext]?[hash]',
                },
                //和上面一致
            },
            {
                test: /\.vue$/,
                loader: 'vue-loader',
                //这一个loader当然是vue项目必须的加载器啦,不加其他规则的话,
                //简单的这样引入就可以了,vue-loader会把vue单文件直接转成js。
            },
        ],
    },
    plugins:[new HtmlWebpackPlugin()],
    resolve: {
        extensions: ['.tsx', '.ts', '.js'],
    },
}
 
module.exports = config
}

 

Rollup:

input:入口文件

output: 输出

plugins:解析模块,并且做优化

export default {
   input: 'src/index.ts',  // //需要打包的文件
  output: {
    file: 'lib/index.js', // 输出文件
    format: 'esm', // immediately-invoked function expression — suitable for <script> tags
  },
  plugins: [
    resolve(), // tells Rollup how to find date-fns in node_modules
    commonjs(), // converts date-fns to ES modules
    ts(),
    babel({
      babelHelpers: 'runtime',
    }),
    production && terser(), // minify, but only in production
  ],
  external: ['axios'],  // 外部
}