静修丶

静修丶 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织 java-http.github.io/ 编辑
编辑

前端小白一枚

个人动态

静修丶 关注了问题 · 11月9日

npx 运行的时候 如何执行包里的某个命令

有一个名为 foo 的库,他的 package.jsonbin 属性是这样的

"bin": {
 "bar": "./help.js",
 "bar-init": "./index.js",
},

当我使用 npx 的命令时: npx foo 他会自动使用 bin 里面的 bar 指令, 但是当我想要使用另一个指令: bar-init 的时候,该怎么配置使用

我使用了 npx foo bar-init , npx foo --shell bar-init , npx foo -c bar-init 结果运行的都是 bar 指令

关注 2 回答 0

静修丶 赞了回答 · 10月28日

process.env.NODE_ENV 在浏览器下和 node 环境下读取的是哪个文件设置的值?

首先楼主明白的,process.envNode 环境的定义的变量,浏览器环境肯定是返回 undefined 的。
1,首先怎样让 Node 去识别当前运行的是哪个变量,是 “development” 还是 “production”,这个是通过 cross-env 来定义的。它是一个跨平台的(windows or maxos or liunx),识别当前运行的环境变量的工具。当然去查看下它的文档说明,更直观。

2,怎样让浏览器去识别当前运行的环境变量呢?其实 webpack 有个插件,就是 webpack.DefinePlugin

new webpack.DefinePlugin({
      // process.env 这个就会被浏览器环境所识别
      'process.env': require('./config/dev.env')
    }),

所以浏览器才会认识 process.env.NODE_ENV


如有帮助,麻烦点击下采纳,谢谢~

关注 5 回答 3

静修丶 关注了问题 · 9月16日

解决react 组件中确定是否含有某个特定的子组件

react 组件中确定是否含有某个特定的子组件

最近在写一个组件,但是需要知道这个组件里面是否含有特定的子组件,目前找到的一种方法是使用组件的type.name的方式进行判断,想问一下有没有更好的方式进行判断

相关代码

 // 父组件
    class Layout extends Component {
    
        static propTypes = {
            accordion: PropTypes.bool,
        }
    
        static defaultProps = {
            accordion: false,
        }
    
        // 判断是否含有Sider子组件
        judeSider = () => {
            let hasSider = false
            React.Children.forEach(this.props.children, (child) => {
                if (child.type.name === 'Sider') { //这里使用child的type.name进行判断
                    hasSider = true
                }
            })
            return hasSider
        }
    
        render() {
            const hasSider = this.judeSider()
            return <div className={hasSider ? styles.layoutHasSider : styles.layout}>{this.props.children}</div>
        }
    }
    
    // 子组件
    class Sider extends Component {
    
    static propTypes = {
        collapsible: PropTypes.bool,
        collapsed: PropTypes.bool,
        width: PropTypes.string
    }
    
    static defaultProps = {
        collapsible: false,
        collapsed: false,
        width: '200px'
    }
    
    render() {
        const { className, children, style, width, collapsible, collapsed, ...others } = this.props
        const cls = classNames(className, styles.sider, {
            [styles.collapsible]: collapsible,
            [styles.collapsed]: collapsed
        })
        const realStyle = collapsed ? {...style, width:'0px'} : {...style, width: width}
        return <div
            className={cls}
            style={realStyle}
            {...others}
        >
            {children}
        </div>
    }
}

如果有哪位前辈知道的话麻烦告知一下,感激不尽!

关注 3 回答 1

静修丶 赞了文章 · 9月4日

你真的了解回流和重绘吗

回流和重绘可以说是每一个web开发者都经常听到的两个词语,我也不例外,可是我之前一直不是很清楚这两步具体做了什么事情。最近由于部门内部要做分享,所以对其进行了一些研究,看了一些博客和书籍,整理了一些内容并且结合一些例子,写了这篇文章,希望可以帮助到大家。

浏览器的渲染过程

本文先从浏览器的渲染过程来从头到尾的讲解一下回流重绘,如果大家想直接看如何减少回流和重绘,可以跳到后面。(这个渲染过程来自MDN

webkit渲染过程

从上面这个图上,我们可以看到,浏览器渲染过程如下:

  1. 解析HTML,生成DOM树,解析CSS,生成CSSOM树
  2. 将DOM树和CSSOM树结合,生成渲染树(Render Tree)
  3. Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)
  4. Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
  5. Display:将像素发送给GPU,展示在页面上。(这一步其实还有很多内容,比如会在GPU将多个合成层合并为同一个层,并展示在页面中。而css3硬件加速的原理则是新建合成层,这里我们不展开,之后有机会会写一篇博客)

渲染过程看起来很简单,让我们来具体了解下每一步具体做了什么。

生成渲染树

生成渲染树

为了构建渲染树,浏览器主要完成了以下工作:

  1. 从DOM树的根节点开始遍历每个可见节点。
  2. 对于每个可见的节点,找到CSSOM树中对应的规则,并应用它们。
  3. 根据每个可见节点以及其对应的样式,组合生成渲染树。

第一步中,既然说到了要遍历可见的节点,那么我们得先知道,什么节点是不可见的。不可见的节点包括:

  • 一些不会渲染输出的节点,比如script、meta、link等。
  • 一些通过css进行隐藏的节点。比如display:none。注意,利用visibility和opacity隐藏的节点,还是会显示在渲染树上的。只有display:none的节点才不会显示在渲染树上。

注意:渲染树只包含可见的节点

回流

前面我们通过构造渲染树,我们将可见DOM节点以及它对应的样式结合起来,可是我们还需要计算它们在设备视口(viewport)内的确切位置和大小,这个计算的阶段就是回流。

为了弄清每个对象在网站上的确切大小和位置,浏览器从渲染树的根节点开始遍历,我们可以以下面这个实例来表示:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Critial Path: Hello world!</title>
  </head>
  <body>
    <div style="width: 50%">
      <div style="width: 50%">Hello world!</div>
    </div>
  </body>
</html>

我们可以看到,第一个div将节点的显示尺寸设置为视口宽度的50%,第二个div将其尺寸设置为父节点的50%。而在回流这个阶段,我们就需要根据视口具体的宽度,将其转为实际的像素值。(如下图)

重绘

最终,我们通过构造渲染树和回流阶段,我们知道了哪些节点是可见的,以及可见节点的样式和具体的几何信息(位置、大小),那么我们就可以将渲染树的每个节点都转换为屏幕上的实际像素,这个阶段就叫做重绘节点。

既然知道了浏览器的渲染过程后,我们就来探讨下,何时会发生回流重绘。

何时发生回流重绘

我们前面知道了,回流这一阶段主要是计算节点的位置和几何信息,那么当页面布局和几何信息发生变化的时候,就需要回流。比如以下情况:

  • 添加或删除可见的DOM元素
  • 元素的位置发生变化
  • 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
  • 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代。
  • 页面一开始渲染的时候(这肯定避免不了)
  • 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)

注意:回流一定会触发重绘,而重绘不一定会回流

根据改变的范围和程度,渲染树中或大或小的部分需要重新计算,有些改变会触发整个页面的重排,比如,滚动条出现的时候或者修改了根节点。

浏览器的优化机制

现代的浏览器都是很聪明的,由于每次重排都会造成额外的计算消耗,因此大多数浏览器都会通过队列化修改并批量执行来优化重排过程。浏览器会将修改操作放入到队列里,直到过了一段时间或者操作达到了一个阈值,才清空队列。但是!当你获取布局信息的操作的时候,会强制队列刷新,比如当你访问以下属性或者使用以下方法:

  • offsetTop、offsetLeft、offsetWidth、offsetHeight
  • scrollTop、scrollLeft、scrollWidth、scrollHeight
  • clientTop、clientLeft、clientWidth、clientHeight
  • getComputedStyle()
  • getBoundingClientRect
  • 具体可以访问这个网站:https://gist.github.com/pauli...

以上属性和方法都需要返回最新的布局信息,因此浏览器不得不清空队列,触发回流重绘来返回正确的值。因此,我们在修改样式的时候,最好避免使用上面列出的属性,他们都会刷新渲染队列。如果要使用它们,最好将值缓存起来。

减少回流和重绘

好了,到了我们今天的重头戏,前面说了这么多背景和理论知识,接下来让我们谈谈如何减少回流和重绘。

最小化重绘和重排

由于重绘和重排可能代价比较昂贵,因此最好就是可以减少它的发生次数。为了减少发生次数,我们可以合并多次对DOM和样式的修改,然后一次处理掉。考虑这个例子

const el = document.getElementById('test');
el.style.padding = '5px';
el.style.borderLeft = '1px';
el.style.borderRight = '2px';

例子中,有三个样式属性被修改了,每一个都会影响元素的几何结构,引起回流。当然,大部分现代浏览器都对其做了优化,因此,只会触发一次重排。但是如果在旧版的浏览器或者在上面代码执行的时候,有其他代码访问了布局信息(上文中的会触发回流的布局信息),那么就会导致三次重排。

因此,我们可以合并所有的改变然后依次处理,比如我们可以采取以下的方式:

  • 使用cssText

    const el = document.getElementById('test');
    el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;';
  • 修改CSS的class

    const el = document.getElementById('test');
    el.className += ' active';

批量修改DOM

当我们需要对DOM对一系列修改的时候,可以通过以下步骤减少回流重绘次数:

  1. 使元素脱离文档流
  2. 对其进行多次修改
  3. 将元素带回到文档中。

该过程的第一步和第三步可能会引起回流,但是经过第一步之后,对DOM的所有修改都不会引起回流,因为它已经不在渲染树了。

有三种方式可以让DOM脱离文档流:

  • 隐藏元素,应用修改,重新显示
  • 使用文档片段(document fragment)在当前DOM之外构建一个子树,再把它拷贝回文档。
  • 将原始元素拷贝到一个脱离文档的节点中,修改节点后,再替换原始的元素。

考虑我们要执行一段批量插入节点的代码:

function appendDataToElement(appendToElement, data) {
    let li;
    for (let i = 0; i < data.length; i++) {
        li = document.createElement('li');
        li.textContent = 'text';
        appendToElement.appendChild(li);
    }
}

const ul = document.getElementById('list');
appendDataToElement(ul, data);

如果我们直接这样执行的话,由于每次循环都会插入一个新的节点,会导致浏览器回流一次。

我们可以使用这三种方式进行优化:

隐藏元素,应用修改,重新显示

这个会在展示和隐藏节点的时候,产生两次重绘

function appendDataToElement(appendToElement, data) {
    let li;
    for (let i = 0; i < data.length; i++) {
        li = document.createElement('li');
        li.textContent = 'text';
        appendToElement.appendChild(li);
    }
}
const ul = document.getElementById('list');
ul.style.display = 'none';
appendDataToElement(ul, data);
ul.style.display = 'block';

使用文档片段(document fragment)在当前DOM之外构建一个子树,再把它拷贝回文档

const ul = document.getElementById('list');
const fragment = document.createDocumentFragment();
appendDataToElement(fragment, data);
ul.appendChild(fragment);

将原始元素拷贝到一个脱离文档的节点中,修改节点后,再替换原始的元素。

const ul = document.getElementById('list');
const clone = ul.cloneNode(true);
appendDataToElement(clone, data);
ul.parentNode.replaceChild(clone, ul);

对于上述那种情况,我写了一个demo来测试修改前和修改后的性能。然而实验结果不是很理想。

原因:原因其实上面也说过了,浏览器会使用队列来储存多次修改,进行优化,所以对这个优化方案,我们其实不用优先考虑。

避免触发同步布局事件

上文我们说过,当我们访问元素的一些属性的时候,会导致浏览器强制清空队列,进行强制同步布局。举个例子,比如说我们想将一个p标签数组的宽度赋值为一个元素的宽度,我们可能写出这样的代码:

function initP() {
    for (let i = 0; i < paragraphs.length; i++) {
        paragraphs[i].style.width = box.offsetWidth + 'px';
    }
}

这段代码看上去是没有什么问题,可是其实会造成很大的性能问题。在每次循环的时候,都读取了box的一个offsetWidth属性值,然后利用它来更新p标签的width属性。这就导致了每一次循环的时候,浏览器都必须先使上一次循环中的样式更新操作生效,才能响应本次循环的样式读取操作。每一次循环都会强制浏览器刷新队列。我们可以优化为:

const width = box.offsetWidth;
function initP() {
    for (let i = 0; i < paragraphs.length; i++) {
        paragraphs[i].style.width = width + 'px';
    }
}

同样,我也写了个demo来比较两者的性能差异。你可以自己点开这个demo体验下。这个对比差距就比较明显。

对于复杂动画效果,使用绝对定位让其脱离文档流

对于复杂动画效果,由于会经常的引起回流重绘,因此,我们可以使用绝对定位,让它脱离文档流。否则会引起父元素以及后续元素频繁的回流。这个我们就直接上个例子

打开这个例子后,我们可以打开控制台,控制台上会输出当前的帧数(虽然不准)。

image-20181210223750055

从上图中,我们可以看到,帧数一直都没到60。这个时候,只要我们点击一下那个按钮,把这个元素设置为绝对定位,帧数就可以稳定60。

css3硬件加速(GPU加速)

比起考虑如何减少回流重绘,我们更期望的是,根本不要回流重绘。这个时候,css3硬件加速就闪亮登场啦!!

划重点:使用css3硬件加速,可以让transform、opacity、filters这些动画不会引起回流重绘 。但是对于动画的其它属性,比如background-color这些,还是会引起回流重绘的,不过它还是可以提升这些动画的性能。

本篇文章只讨论如何使用,暂不考虑其原理,之后有空会另外开篇文章说明。

如何使用

常见的触发硬件加速的css属性:

  • transform
  • opacity
  • filters
  • Will-change

效果

我们可以先看个例子。我通过使用chrome的Performance捕获了一段时间的回流重绘情况,实际结果如下图:

image-20181210225609533

从图中我们可以看出,在动画进行的时候,没有发生任何的回流重绘。如果感兴趣你也可以自己做下实验。

重点

  • 使用css3硬件加速,可以让transform、opacity、filters这些动画不会引起回流重绘
  • 对于动画的其它属性,比如background-color这些,还是会引起回流重绘的,不过它还是可以提升这些动画的性能。

css3硬件加速的坑

  • 如果你为太多元素使用css3硬件加速,会导致内存占用较大,会有性能问题。
  • 在GPU渲染字体会导致抗锯齿无效。这是因为GPU和CPU的算法不同。因此如果你不在动画结束的时候关闭硬件加速,会产生字体模糊。

总结

本文主要讲了浏览器的渲染过程、浏览器的优化机制以及如何减少甚至避免回流和重绘,希望可以帮助大家更好的理解回流重绘。

参考文献

本文地址在->本人博客地址, 欢迎给个 start 或 follow

查看原文

赞 224 收藏 153 评论 17

静修丶 赞了文章 · 6月5日

SVG之ViewBox

最近开始看SVG Essentials。找不到中文版的,逼着自己看原版书,进度比较慢,不过边学技术边学英语还是挺有成就感的^_^。
目前看到Chapter 4,有必要先停下来整理下viewport这个知识点,个人感觉挺关键的。

viewBox用来干嘛?

比方说,我用svg画了个半径200px的圆

<circle cx="200" cy="200" r="200" fill="#fdd" stroke="none"></circle>

如果是在一个400*400的画布上,圆正好撑满整个画布,挺好的。
好了,然后我要把这个圆嵌入到自己的页面里的svg标签里去,页面的svg标签尺寸是由实际业务需要来定的,不一定正好是,可能大可能小,还可能不是正方形。

<svg style="width:150px; height:300px">
    <circle cx="200" cy="200" r="200" fill="#fdd" stroke="none"></circle>
</svg>

正常情况下面浏览器中显示是这个样子的
clipboard.png

产品见了肯定不乐意啊,“我要整个圆啊,谁要这种残次品啊”。
咋整的,要么把这个圆的代码改了吧。
可如果不是简简单单一个圆呢,而是一大堆复杂代码,改个鬼啊。
呵呵~此刻,便是viewBox用武之地!

<svg style="width:150px; height:300px" viewBox="0 0 400 400">
    <circle cx="200" cy="200" r="200" fill="#fdd" stroke="none"></circle>
</svg>

摇身一变就成这样了
clipboard.png

圆变小了,展示全了,还垂直居中了。
一件一件事来,看看发生了什么。

viewBox的四个参数分别代表:最小X轴数值;最小y轴数值;宽度;高度。
前两个暂时用不到,个人理解除非要对内部svg做整体位移,否则一般都是0 0,暂时先不做解释,重点关注后两个参数。
想象一下viewBox是个400*400的正方形,但是单位不是px,也不是任何一个css单位,就当是一个假的单位吧。在viewBox放了一个圆,这个圆的半径是200,单位也不是px,而是变成了和viewBox的单位一模一样的那个假的单位。为啥说是假的呢?因为这个单位代表的长度是会变的,接着看。
svg有个特点,在默认情况下,会调整这个viewBox的大小,让这个viewBox正好能被放进svg里去。拿上面例子来说,viewBox是个正方形,而svg的宽度比高度小,所以就把viewBox的大小缩小到和svg宽度一样,就能正好将viewBox放进svg里来了。所以现在viewBox的实际大小是个150px*150px的正方形。
所以现在可以确定的是,viewBox的一个单位代表的长度 = 150px/400 = 0.375px。
而viewBox内部的所有数值*0.375px才是真正的长度。那个circle的圆心实际上是在坐标75px, 75px的位置上,半径为75px。

圆的具体大小大概就是这么回事。
再看svg那个默认调整viewBox大小是怎么回事儿。
隐藏属性preserveAspectRatio粉墨登场!

preserveAspectRatio又用来干嘛?

英文直译:维持外观比例。好像还挺语义化的一属性名。
对,这是个属性,它和viewBox的关系特别密切。即使不显示声明这个preserveAspectRatio属性,viewBox也是会有这个属性的默认设置的,即preserveAspectRatio="xMidYMid meet"
显示声明方式如下:

<svg style="width:150px; height:300px" viewBox="0 0 400 400" preserveAspectRatio="xMidYMid meet">

说说preserveAspectRatio的这个参数。

第一个参数有9个不同值可选

xMinYMin,
xMinYMid,
xMinYMax,
xMidYMin,
xMidYMid,
xMidYMax,
xMaxYMin,
xMaxYMid,
xMaxYMax

是不是特有规律啊?x和y表示对齐的轴线,min,mid,max表示对齐的方式。min是往坐标小的方向对齐;mid居中对齐;max是往坐标大的方向对齐(顺带一提svg的坐标0刻度在左上角)。

第二个参数有两个值可选:meet和slice
meet就是前面那种自动调整viewBox到可以在svg画布中完全展示。非常类似css里background-size:contain
而slice是自动调整viewBox到撑满整个svg画布。非常类似background-size:cover

再回看第二张图里那个垂直居中的圆,就能明白为什么会“圆变小了,展示全了,还垂直居中了”。

其实说是preserveAspectRatio的第一个参数有9种值,在确定svg画布宽大于高,或者高大于宽的情况下,可以缩减到就3种值,其他值都是重复的,但在不确定画布尺寸时,还是要明确需求选择合适的参数值。

preserveAspectRatio还有个单独使用的参数:"none"。
这种时候viewBox会被拉伸到和svg画布相同尺寸,而内部的所有svg元素也会被等比拉伸,而不是维持原有比例。

第一个知识点整理完毕,待续~ 必须啃完人生第一本原版技术书!!!

查看原文

赞 74 收藏 37 评论 8

静修丶 赞了文章 · 5月28日

webpack4 es module打包后代码分析

1.单一模块

index.js

export let num = 9;
let def = 44;
export default def;

打包后输出的文件(eval sourcemap部分删除)

(function(modules) { // webpackBootstrap

    var installedModules = {};

    function __webpack_require__(moduleId) {

        if(installedModules[moduleId]) {
            return installedModules[moduleId].exports;
        }

        var module = installedModules[moduleId] = {
            i: moduleId,  
            l: false,     
            exports: {}
        };

        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);console.log(module)

        module.l = true;

        return module.exports;
    }

    __webpack_require__.m = modules;

    __webpack_require__.c = installedModules;

    __webpack_require__.d = function(exports, name, getter) {
        if(!__webpack_require__.o(exports, name)) {
            Object.defineProperty(exports, name, {
                configurable: false,
                enumerable: true,
                get: getter
            });
        }
    };

    __webpack_require__.r = function(exports) {
        Object.defineProperty(exports, '__esModule', { value: true });
    };

    __webpack_require__.n = function(module) {
        var getter = module && module.__esModule ?
            function getDefault() { return module['default']; } :
            function getModuleExports() { return module; };
        __webpack_require__.d(getter, 'a', getter);
        return getter;
    };

    __webpack_require__.o = function(object, property) {
        return Object.prototype.hasOwnProperty.call(object, property);
    };

    __webpack_require__.p = "";

    return __webpack_require__(__webpack_require__.s = "./src/index.js");
})({
    "./src/index.js": (function(module, __webpack_exports__, __webpack_require__) {

        "use strict";

        eval("__webpack_require__.r(__webpack_exports__);\n __webpack_require__.d(__webpack_exports__, \"num\", function() { return num; });\n let num = 9;\r\n let def = 44;\r\n __webpack_exports__[\"default\"] = (def);");
    })
});

打包后的文件为一个自执行的函数匿名,参数为入口文件及其依赖的模块(此处没有依赖模块)

(function(modules) {
    var installedModules = {};
    
    function __webpack_require__(moduleId) {
        ...
    }
    
    __webpack_require__.m = modules;
    
    __webpack_require__.c = installedModules;
    
    __webpack_require__.d = function(){
        ...
    }
    
    __webpack_require__.r = function(){
        ...
    }
    
    __webpack_require__.n = function(){
        ...
    }
    
    __webpack_require__.o = function(){
        ...
    }
    
    __webpack_require__.p = '';
    
    return __webpack_require__(__webpack_require__.s = "./src/index.js");
})({
    "./src/index.js": (function(module, __webpack_exports__, __webpack_require__) {
        "use strict";
        eval("...");

})

匿名函数声明了一个installedModules对象,用于缓存加载进来的模块(加载进来的模块不一定加载完成)
声明了__webpack_require__,并为其添加了一些方法和属性:m、c、d、r、n、o
m:保存传入的模块对象
c:保存缓存的模块
d:在exports对象上添加属性
r:在exports对象上添加__esModule,用于标识es6模块
n:getDefaultExport
o:判断对象上是否有某一属性

__webpack_require__函数

function __webpack_require__(moduleId) {

        //是否有缓存
        if(installedModules[moduleId]) {
            return installedModules[moduleId].exports;
        }

        var module = installedModules[moduleId] = {
            i: moduleId,   //模块id
            l: false,     //模块是否已加载
            exports: {}
        };

        //加载模块
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

        module.l = true;

        return module.exports;
    }

index.js模块里的东西通过modules[moduleId].call()中加载进来,最终的module包含如下内容
图片描述

参数

{
    "./src/index.js": (function(module, __webpack_exports__, __webpack_require__) {

        //es6模块为严格模式
        "use strict";

        //每个模块都被eval执行
        eval("__webpack_require__.r(__webpack_exports__);\n __webpack_require__.d(__webpack_exports__, \"num\", function() { return num; });\n let num = 9;\r\n let def = 44;\r\n __webpack_exports__[\"default\"] = (def);");
        
        /*eval中执行了以下内容
        * __webpack_require__.r(__webpack_exports__);
        * __webpack_require__.d(__webpack_exports__, "num", function() { return num; });
        * let num = 9;
        * let def = 44;
        * __webpack_exports__["default"] = def;
        * */
    }

"./src/index.js"为文件路径也是该模块的id
eval中为通过__webpack_require__.r给export添加__esModule属性,并定义num、default属性

2.依赖模块

chunk.js

let def = 44;
export default def;

index.js

import def from './chunk1.js';
export let num = 9;

打包后的文件(自执行函数体部分不变,仅展示参数部分)

{
    "./src/chunk1.js": (function(module, __webpack_exports__, __webpack_require__) {
        "use strict";
        eval("__webpack_require__.r(__webpack_exports__);\n let def = 44;\r\n __webpack_exports__[\"default\"] = (def);");
    }),

    "./src/index.js": (function(module, __webpack_exports__, __webpack_require__) {
        "use strict";
        eval("__webpack_require__.r(__webpack_exports__);\n __webpack_require__.d(__webpack_exports__, \"num\", function() { return num; });\n var _chunk1_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(\"./src/chunk.js\");\n\r\n let num = 9;\r\n console.log(_chunk1_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])");
    })
}

index.js模块中调用__webpack_require__("./src/chunk1.js")加载chunk.js,最终installedModules如下
clipboard.png

3.依赖重复模块

index.js

import chunk1 from './chunk1.js';
import chunk2 from './chunk2.js';
export let index1 = 9;
console.log(chunk1);
console.log(chunk2);

chunk1.js

import chunk2 from './chunk2.js';
let chunk1 = 1;
export default chunk1;

chunk2.js

let chunk2 = 2;
export default chunk2;

打包后的参数部分

({
    "./src/chunk1.js": (function(module, __webpack_exports__, __webpack_require__) {
        "use strict";
        eval("__webpack_require__.r(__webpack_exports__);\n var _chunk2_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(\"./src/chunk2.js\");\n\r\n let chunk1 = 1;\r\n __webpack_exports__[\"default\"] = (chunk1);");
    }),

    "./src/chunk2.js": (function(module, __webpack_exports__, __webpack_require__) {
        "use strict";
        eval("__webpack_require__.r(__webpack_exports__);\n let chunk2 = 2;\r\n __webpack_exports__[\"default\"] = (chunk2);");

    }),

    "./src/index.js": (function(module, __webpack_exports__, __webpack_require__) {
        "use strict";
        eval("__webpack_require__.r(__webpack_exports__);\n __webpack_require__.d(__webpack_exports__, \"index1\", function() { return index1; });\n var _chunk1_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(\"./src/chunk1.js\");\n var _chunk2_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(\"./src/chunk2.js\");\n\r let index1 = 9;\r\n console.log(_chunk1_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"]);\r\n console.log(_chunk2_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"]);");

    })
});

模块不会重复加载,chunk1中import chunk2.js __webpack_require__直接返回缓存中的数据

4.入口为数组

(function(modules) {
    ...
    return __webpack_require__(__webpack_require__.s = 0);
})({
    "./src/chunk1.js": (function(module, __webpack_exports__, __webpack_require__) {
        "use strict";
        eval("__webpack_require__.r(__webpack_exports__);\n let chunk1 = 1;\r\n __webpack_exports__[\"default\"] = (chunk1);");
    }),

    "./src/index.js": (function(module, __webpack_exports__, __webpack_require__) {
        "use strict";
        eval("__webpack_require__.r(__webpack_exports__);\n __webpack_require__.d(__webpack_exports__, \"index1\", function() { return index1; });\n var _chunk1_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(\"./src/chunk1.js\");\n\r\nlet index1 = 9;\r\n console.log(_chunk1_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"]);");
    }),

    "./src/index2.js": (function(module, __webpack_exports__, __webpack_require__) {
        "use strict";
        eval("__webpack_require__.r(__webpack_exports__);\n __webpack_require__.d(__webpack_exports__, \"index2\", function() { return index2; });\n var _chunk1_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(\"./src/chunk1.js\");\n\r\nlet index2 = 66;");
    }),

    0: (function(module, exports, __webpack_require__) {
        eval("__webpack_require__(\"./src/index.js\");\n module.exports = __webpack_require__(\"./src/index2.js\");");
    })
});

相比字符串入口return的不再是index.js,而是moduleId为0的模块,在0模块中触发index、index2模块的加载并返回index2模块(数组中的最后一项)

5.使用splitChunks

runtimeChunk为true

index.bundle.js

(function(modules) {
    // install a JSONP callback for chunk loading
    function webpackJsonpCallback(data) {
        var chunkIds = data[0];
        var moreModules = data[1];
        var executeModules = data[2];
        // add "moreModules" to the modules object,
        // then flag all "chunkIds" as loaded and fire callback
        var moduleId, chunkId, i = 0, resolves = [];
        for(;i < chunkIds.length; i++) {
            chunkId = chunkIds[i];
            if(installedChunks[chunkId]) {
                resolves.push(installedChunks[chunkId][0]);
            }
            installedChunks[chunkId] = 0;
        }
        for(moduleId in moreModules) {
            if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
                modules[moduleId] = moreModules[moduleId];
            }
        }
        if(parentJsonpFunction) parentJsonpFunction(data);
        while(resolves.length) {
            resolves.shift()();
        }

        // add entry modules from loaded chunk to deferred list
        deferredModules.push.apply(deferredModules, executeModules || []);

        // run deferred modules when all chunks ready
        return checkDeferredModules();
    };

    function checkDeferredModules() {
        var result;
        for(var i = 0; i < deferredModules.length; i++) {
            var deferredModule = deferredModules[i];
            var fulfilled = true;
            for(var j = 1; j < deferredModule.length; j++) {
                var depId = deferredModule[j];
                if(installedChunks[depId] !== 0) fulfilled = false;
            }
            if(fulfilled) {
                deferredModules.splice(i--, 1);
                result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
            }
        }

        return result;
    }

    var installedModules = {};

    // object to store loaded and loading chunks
    // undefined = chunk not loaded, null = chunk preloaded/prefetched
    // Promise = chunk loading, 0 = chunk loaded
    var installedChunks = {
        "runtime~main": 0
    };

    // script path function
    function jsonpScriptSrc(chunkId) {
        return __webpack_require__.p + "" + chunkId + ".index.bundle.js"
    }

    var deferredModules = [];

    // The require function
    function __webpack_require__(moduleId) {

        // Check if module is in cache
        if(installedModules[moduleId]) {
            return installedModules[moduleId].exports;
        }
        // Create a new module (and put it into the cache)
        var module = installedModules[moduleId] = {
            i: moduleId,
            l: false,
            exports: {}
        };

        // Execute the module function
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

        // Flag the module as loaded
        module.l = true;

        // Return the exports of the module
        return module.exports;
    }

    __webpack_require__.m = modules;

    __webpack_require__.c = installedModules;

    __webpack_require__.d = function(exports, name, getter) {
        if(!__webpack_require__.o(exports, name)) {
            Object.defineProperty(exports, name, {
                configurable: false,
                enumerable: true,
                get: getter
            });
        }
    };

    __webpack_require__.r = function(exports) {
        Object.defineProperty(exports, '__esModule', { value: true });
    };

    __webpack_require__.n = function(module) {
        var getter = module && module.__esModule ?
            function getDefault() { return module['default']; } :
            function getModuleExports() { return module; };
        __webpack_require__.d(getter, 'a', getter);
        return getter;
    };

    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };

    __webpack_require__.p = "";

    var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];

    var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);

    //改写push方法,main中调用该方法
    jsonpArray.push = webpackJsonpCallback;
    jsonpArray = jsonpArray.slice();

    //main.index.bundle.js push之前jsonpArray为空
    for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);

    //保留原始的push方法,webpackJsonpCallback中通过该方法将main.inde.bundle.js中的数组添加进window["webpackJsonp"]
    var parentJsonpFunction = oldJsonpFunction;

    // run deferred modules from other chunks
    checkDeferredModules();
})([]);

整体为一个自执行函数,因此时参数为空,index.bundle.js中只是绑定了一些属性、方法,并未加载具体的模块。
installedModules缓存已加载的模块
installedChunks缓存chunnk,值为undefined表示模块尚未加载,null表示preloaded/prefetched,promise表示加载中,0表示已加载
webpackJsonpCallback中处理数据绑定及回调
checkDeferredModules中检测chunks的加载情况,全部loaded后调用__webpack_require__('./src/index.js')处理具体模块的加载(./src/index.js为入口模块的id)

main.index.bundle.js

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([
    ["main"],
    {
        "./src/chunk1.js": (function(module, __webpack_exports__, __webpack_require__) {
            "use strict";
            eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _chunk2_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./chunk2.js */ \"./src/chunk2.js\");\n\r\nlet chunk1 = 1;\r\n/* harmony default export */ __webpack_exports__[\"default\"] = (chunk1);\n\n//# sourceURL=webpack:///./src/chunk1.js?");
        }),

        "./src/chunk2.js": (function(module, __webpack_exports__, __webpack_require__) {
            "use strict";
            eval("__webpack_require__.r(__webpack_exports__);\nlet chunk2 = 2;\r\n/* harmony default export */ __webpack_exports__[\"default\"] = (chunk2);\n\n//# sourceURL=webpack:///./src/chunk2.js?");
        }),

        "./src/index.js": (function(module, __webpack_exports__, __webpack_require__) {
            "use strict";
            eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"index1\", function() { return index1; });\n/* harmony import */ var _chunk1_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./chunk1.js */ \"./src/chunk1.js\");\n/* harmony import */ var _chunk2_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./chunk2.js */ \"./src/chunk2.js\");\n\r\n\r\nlet index1 = 9;\r\nconsole.log(_chunk1_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"]);\n\n//# sourceURL=webpack:///./src/index.js?");
        })
    },
    [["./src/index.js","runtime~main"]]
]);

window["webpackJsonp"].push会调用webpackJsonpCallback方法

6.动态加载

index.js

import chunk1 from './chunk1.js';
export let index1 = 9;
import('./chunk2.js').then(res=>{
    console.log(res);
}).catch(e=>{
    console.log(e)
})

打包后的代码仅main.index.bundle.js的"./src/index.js"eval部分不同

__webpack_require__.r(__webpack_exports__);
        __webpack_require__.d(__webpack_exports__, "index1", function() { return index1; });
        var _chunk1_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/chunk1.js");
        let index1 = 9;
        Promise.resolve()
            .then(__webpack_require__.bind(null, "./src/chunk2.js"))
            .then(res=>{
                console.log(res);
            })
            .catch(e=>{
                console.log(e)
            });

import()的promise直接resolve,回调在then(__webpack_require__.bind(null, "./src/chunk2.js"))后执行

参考文章:https://segmentfault.com/a/11...

查看原文

赞 5 收藏 3 评论 0

静修丶 赞了回答 · 5月23日

react useState 异步回调取不到最新值?见代码

你的 setFields 在闭包中,会从闭包作用域取 fields 的值。解决办法是使用函数当作 setFields 的参数。

setFields(fields => [...fields, 1])

关注 4 回答 3

静修丶 赞了回答 · 5月7日

webpack plugin执行顺序问题

Webpack: Does the order of plugins matter?
可以看看这个回答,有顺序的区分。不过你用的plugin会自己绑定相应的事件,所以一般不用关注。

关注 5 回答 2

静修丶 赞了回答 · 4月26日

解决如何让webpack HtmlWebpackPlugin插件生成html插入js 的时候 按chunks 顺序插入?

我也遇过,最后找到方法,使用chunksSortMode

    new HtmlWebpackPlugin({
        ...
        chunksSortMode: function (chunk1, chunk2) {
            var order = ['common', 'public', 'index'];
            var order1 = order.indexOf(chunk1.names[0]);
            var order2 = order.indexOf(chunk2.names[0]);
            return order1 - order2;  
        }
    })

关注 7 回答 6

静修丶 关注了问题 · 4月6日

解决请问这段代码是什么意思?

代码如下:

const requireAll = requireContext => requireContext.keys().map(requireContext)
const req = require.context('./svg', false, /\.svg$/)
requireAll(req)

keys方法返回的是一个Array Iterator,map方法里面的参数应该是一个函数才对,这两个结合在一起好难理解是干嘛的

关注 9 回答 3

认证与成就

  • 获得 59 次点赞
  • 获得 349 枚徽章 获得 10 枚金徽章, 获得 110 枚银徽章, 获得 229 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2015-08-04
个人主页被 2k 人浏览