前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >深度解读 Vite 的依赖扫描?

深度解读 Vite 的依赖扫描?

原创
作者头像
CandyTong
发布于 2022-08-07 15:58:05
发布于 2022-08-07 15:58:05
1.4K00
代码可运行
举报
运行总次数:0
代码可运行

当我们首次运行 Vite 的时候,Vite 会执行依赖预构建,目的是为了兼容 CommonJS 和 UMD,以及提升性能

要对依赖进行预构建,首先要搞清楚这两个问题:

  1. 预构建的内容是什么?/ 哪些模块需要进行预构建?
  2. 如何找到需要预构建的模块?

这两个问题,其实就是依赖扫描的内容以及实现方式

本文会深入地讲解依赖扫描的实现细节,最终的扫描结果是一个包含多个模块的名字的对象,不涉及预构建的过程、预构建产物如何是使用的。如果对该部分内容感兴趣,可以关注我,等待后续文章。

依赖预构建的内容

一个项目中,存在非常多的模块,并不是所有模块都会被预构建。只有 bare import(裸依赖)会执行依赖预构建

什么是 bare import ?

直接看下面这个例子

代码语言:typescript
AI代码解释
复制
// vue 是 bare import
import xxx from "vue"
import xxx from "vue/xxx"

// 以下不是裸依赖
import xxx from "./foo.ts" 
import xxx from "/foo.ts" 

可以简单的划分一下:

  • 用名称去访问的模块是裸模块
  • 用路径去访问的模块,不是 bare import

实际上 Vite 也是这么判断的。

下面是一个常见的 Vue 项目的模块依赖树

依赖扫描的结果如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[ "vue", "axios" ]

为什么只对 bare import 进行预构建?

Node.js 定义了 bare import 的寻址机制 —— 在当前目录下的 node_modules 下寻找,找不到则往上一级目录的 node_modules,直到目录为根路径,不能再往上。

bare import 一般是 npm 安装的模块,是第三方的模块,不是我们自己写的代码,一般情况下是不会被修改的,因此对这部分的模块提前执行构建,有利于提升性能。

相反,如果对开发者写的代码执行预构建,将项目打包成 chunk 文件,当开发者修改代码时,就需要重新执行构建,再打包成 chunk 文件,这个过程反而会影响性能。

monorepo 下的模块也会被预构建吗?

不会。因为 monorepo 的情况下,部分模块虽然是 bare import,但这些模块也是开发者自己写的,不是第三方模块,因此 Vite 没有对该部分的模块执行预构建。

实际上,Vite 会判断模块的实际路径,是否在 node_modules 中

  • 实际路径在 node_modules 的模块会被预构建,这是第三方模块
  • 实际路径不在 node_modules 的模块,证明该模块是通过文件链接,链接到 node_modules 内的(monorepo 的实现方式),是开发者自己写的代码,不执行预构建

依赖扫描

实现思路

我们再来看看这棵模块依赖树:

要扫描出所有的 bare import,就需要遍历整个依赖树,这就涉及到了树的深度遍历

当我们在讨论树的遍历时,一般会关注这两点:

  • 什么时候停止深入?
  • 如何处理叶子节点?

当前叶子节点不需要继续深入遍历的情况:

  • 当遇到 bare import 节点时,记录下该依赖,就不需要继续深入遍历
  • 遇到其他 JS 无关的模块,如 CSS、SVG 等,因为不是 JS 代码,因此也不需要继续深入遍历

当所有的叶子节点遍历完成后,记录的 bare import 对象,就是依赖扫描的结果

依赖扫描的实现思路其实非常容易理解,但实际的处理就不简单了。

我们来看看叶子节点的处理:

  • bare import
代码语言:txt
AI代码解释
复制
可以通过模块 id 判断,**模块 id 不为路径的模块**,就是 bare import。遇到这些模块则**记录依赖,不再深入遍历**。
  • 其他 JS 无关的模块
代码语言:txt
AI代码解释
复制
可以**通过模块的后缀名判断**,例如遇到 `*.css` 的模块,**无需任何处理,不再深入遍历**。
  • JS 模块
代码语言:txt
AI代码解释
复制
要获取 JS 代码中依赖的子模块,就需要**将代码转成 AST,获取其中 import 语句引入的模块,或者正则匹配出所有 import 的模块**,然后**继续深入遍历**这些模块
  • HTML 类型模块
代码语言:txt
AI代码解释
复制
这类模块比较复杂,例如 HTML 或 Vue,里面有一部分是 JS,需要**把这部分 JS 代码提取出来**,然后按 JS 模块进行分析处理,**继续深入遍历**这些模块。这里只需要关心 JS 部分,其他部分不会引入模块。

具体实现

我们已经知道了依赖扫描的实现思路,思路其实不复杂,复杂的是处理过程,尤其是 HTML、Vue 等模块的处理。

Vite 这里用了一种比较巧妙的办法 —— 用 esbuild 工具打包

为什么可以用 esbuild 打包代替深度遍历的过程?

本质上打包过程也是个深度遍历模块的过程,其替代的方式如下:

深度遍历

esbuild 打包

叶子节点的处理

esbuild 可以对每个模块(叶子节点)进行解析和加载 可以通过插件对这两个过程进行扩展,加入一些特殊的逻辑 例如将 html 在加载过程中转换为 js

不深入处理模块

esbuild 可以在解析过程,指定当前解析的模块为 external 则 esbuild 不再深入解析和加载该模块

深入遍历模块

正常解析模块(什么都不做,esbuild 默认行为),返回模块的文件真实路径

这块暂时看不懂没有关系,后面会有例子

各类模块的处理

例子

处理

bare import

vue

在解析过程中,将裸依赖保存到 deps 对象中,设置为 external

其他 JS 无关的模块

less文件

在解析过程中,设置为 external

JS 模块

./mian.ts

正常解析和加载即可,esbuild 本身能处理 JS

html 类型模块

index.htmlapp.vue

在加载过程中,将这些模块加载成 JS

最后 dep 对象中收集到的依赖就是依赖扫描的结果,而这次 esbuild 的打包产物,其实是没有任何作用的,在依赖扫描过程中,我们只关心每个模块的处理过程,不关心构建产物

用 Rollup 处理可以吗?

其实也可以,打包工具基本上都会有解析和加载的流程,也能对模块进行 external

但是 esbuild 性能更好

html 类型的模块处理

这类文件有 htmlvue 等。之前我们提到了要将它们转换成 JS,那么到底要如何转换呢?

由于依赖扫描过程,只关注引入的 JS 模块,因此可以直接丢弃掉其他不需要的内容,直接取其中 JS

html 类型文件(包括 vue)的转换,有两种情况:

  • 每个外部 script,会直接转换为 import 语句,引入外部 script
  • 每个内联 script,其内容将会作为虚拟模块被引入。

什么是虚拟模块?

是模块的内容并非直接从磁盘中读取,而是编译时生成

举个例子,src/main.ts 是磁盘中实际存在的文件,而 virtual-module:D:/project/index.html?id=0 在磁盘中是不存在的,需要借助打包工具(如 esbuild),在编译过程生成。

为什么需要虚拟模块?

因为一个 html 类型文件中,允许有多个 script 标签,多个内联的 script 标签,其内容无法处理成一个 JS 文件 (因为可能会有命名冲突等原因)

既然无法将多个内联 script,就只能将它们分散成多个虚拟模块,然后分别引入了。

源码解析

依赖扫描的入口

下面是扫描依赖的入口函数(为了便于理解,有删减和修改):

代码语言:typescript
AI代码解释
复制
import { build } from 'esbuild'
export async function scanImports(config: ResolvedConfig): Promise<{
  deps: Record<string, string>
  missing: Record<string, string>
}> {
    
  // 将项目中所有的 html 文件作为入口,会排除 node_modules
  let entries: string[] = await globEntries('**/*.html', config)

  // 扫描到的依赖,会放到该对象
  const deps: Record<string, string> = {}
  // 缺少的依赖,用于错误提示
  const missing: Record<string, string> = {}
  
  // esbuild 扫描插件,这个是重点!!!
  const plugin = esbuildScanPlugin(config, container, deps, missing, entries)

  // 获取用户配置的 esbuild 自定义配置,没有配置就是空的
  const { plugins = [], ...esbuildOptions } =
    config.optimizeDeps?.esbuildOptions ?? {}

  await Promise.all(
    // 入口可能不止一个,分别用 esbuid 打包
    entries.map((entry) =>
      // esbuild 打包
      build({
        absWorkingDir: process.cwd(),
        write: false,
        entryPoints: [entry],
        bundle: true,
        format: 'esm',
        // 使用插件
        plugins: [...plugins, plugin],
        ...esbuildOptions
      })
    )
  )

  return {
    deps,
    missing
  }
}

主要流程如下:

  1. 将项目内所有的 html 作为入口文件(排除 node_modules)。
  2. 将每个入口文件,用 esbuild 进行打包

这里的核心其实是 esbuildScanPlugin 插件的实现,它定义了各类模块(叶子节点)的处理方式。

代码语言:typescript
AI代码解释
复制
function esbuildScanPlugin(config, container, deps, missing, entries){}
  • depmissing对象被当做入参传入,在函数中,这两个对象的内容会在打包(插件运行)过程中被修改

esbuild 插件

很多同学可能不知道 esbuild 插件是如何编写的,这里简单介绍一下:

每个模块都会经过解析(resolve)和加载(load)的过程:

  • 解析:将模块路径,解析成文件真实的路径。例如 vue,会解析到实际 node_modules 中的 vue 的入口 js 文件
  • 加载:根据解析的路径,读取文件的内容

插件可以定制化解析和加载的过程,下面是一些插件示例代码:

代码语言:typescript
AI代码解释
复制
const plugin = {
    name: 'xxx',
    setup(build) {
        
        // 定制解析过程,所有的 http/https 的模块,都会被 external
        build.onResolve({ filter: /^(https?:)?\/\// }, ({ path }) => ({
            path,
            external: true
        }))
        
        // 定制解析过程,给所有 less 文件 namespace: less 标记
        build.onResolve({ filter: /.*\.less/ }, args => ({
            path: args.path,
            namespace: 'less',
        }))

        // 定义加载过程:只处理 namespace 为 less 的模块
        build.onLoad({ filter: /.*/, namespace: 'less' }, () => {
            const raw = fs.readFileSync(path, 'utf-8')
            const content = // 省略 less 处理,将 less 处理成 css
            return {
                contents,
                loader: 'css'
        	}
        })
    }
}
  • 通过 onResolveonLoad 定义解析和加载过程
  • onResolve 的第一个参数为过滤条件,第二个参数为回调函数,解析时调用,返回值可以给模块做标记,如 externalnamespace(用于过滤),还需要返回模块的路径
  • 每个模块, onResolve 会被依次调用,直到回调函数返回有效的值,后面的不再调用。如果都没有有效返回,则使用默认的解析方式
  • onLoad 的第一个参数为过滤条件,第二个参数为回调函数,加载时调用,可以读取文件的内容,然后进行处理,最后返回加载的内容
  • 每个模块,onLoad 会被依次调用,直到回调函数返回有效的值,后面的不再调用。如果都没有有效返回,则使用默认的加载方式。

扫描插件的实现

代码语言:typescript
AI代码解释
复制
function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin

部分参数解析:

  • config:Vite 的解析好的用户配置
  • container:这里只会用到 container.resolveId 的方法,这个方法能将模块路径转成真实路径

例如 vue 转成 xxx/node_modules/dist/vue.esm-bundler.js

  • depImports:用于存储扫描到的依赖对象,插件执行过程中会被修改
  • missing:用于存储缺少的依赖的对象,插件执行过程中会被修改
  • entries:存储所有入口文件的数组

esbuild 默认能将模块路径转成真实路径,为什么还要用 container.resolveId

因为 Vite/Rollup 的插件,也能扩展解析的流程,例如 alias 的能力,我们常常会在项目中用 @ 的别名代表项目的 src 路径。

因此不能用 esbuild 原生的解析流程进行解析。

container(插件容器)用于兼容 Rollup 插件生态,用于保证 dev 和 production 模式下,Vite 能有一致的表现。感兴趣的可查看《Vite 是如何兼容 Rollup 插件生态的》

这里 container.resolveId 会被再次包装一成 resolve 函数(多了缓存能力)

代码语言:typescript
AI代码解释
复制
const seen = new Map<string, string | undefined>()
const resolve = async (
    id: string,
    importer?: string,
    options?: ResolveIdOptions
) => {
    const key = id + (importer && path.dirname(importer))
    
    // 如果有缓存,就直接使用缓存
    if (seen.has(key)) {
        return seen.get(key)
    }
    // 将模块路径转成真实路径
    const resolved = await container.resolveId(
        id,
        importer && normalizePath(importer),
        {
            ...options,
            scan: true
        }
    )
    // 缓存解析过的路径,之后可以直接获取
    const res = resolved?.id
    seen.set(key, res)
    return res
  }

那么接下来就是插件的实现了,先回顾一下之前写的各类模块的处理:

例子

处理

bare import

vue

在解析过程中,将裸依赖保存到 deps 对象中,设置为 external

其他 JS 无关的模块

less文件

在解析过程中,设置为 external

JS 模块

./mian.ts

正常解析和加载即可,esbuild 本身能处理 JS

html 类型模块

index.htmlapp.vue

在加载过程中,将这些模块加载成 JS

JS 模块

esbuild 本身就能处理 JS 语法,因此 JS 是不需要任何处理的,esbuild 能够分析出 JS 文件中的依赖,并进一步深入处理这些依赖。

其他 JS 无关的模块

代码语言:typescript
AI代码解释
复制
// external urls
build.onResolve({ filter: /^(https?:)?\/\// }, ({ path }) => ({
    path,
    external: true
}))

// external css 等文件
build.onResolve(
    {
        filter: /\.(css|less|sass|scss|styl|stylus|pcss|postcss|json|wasm)$/
    },
    ({ path }) => ({
        path,
        external: true
    }
)
    
// 省略其他 JS 无关的模块

这部分处理非常简单,直接匹配,然后 external 就行了

bare import

代码语言:typescript
AI代码解释
复制
build.onResolve(
  {
    // 第一个字符串为字母或 @,且第二个字符串不是 : 冒号。如 vite、@vite/plugin-vue
    // 目的是:避免匹配 window 路径,如 D:/xxx 
    filter: /^[\w@][^:]/
  },
  async ({ path: id, importer, pluginData }) => {
    // depImports 为
    if (depImports[id]) {
      return externalUnlessEntry({ path: id })
    }
    // 将模块路径转换成真实路径,实际上调用 container.resolveId
    const resolved = await resolve(id, importer, {
      custom: {
        depScan: { loader: pluginData?.htmlType?.loader }
      }
    })
    
    // 如果解析到路径,证明找得到依赖
    // 如果解析不到路径,则证明找不到依赖,要记录下来后面报错
    if (resolved) {
      if (shouldExternalizeDep(resolved, id)) {
        return externalUnlessEntry({ path: id })
      }
      // 如果模块在 node_modules 中,则记录 bare import
      if (resolved.includes('node_modules')) {
        // 记录 bare import
        depImports[id] = resolved

        return {
        	path,
        	external: true
   		}
      } 
      // isScannable 判断该文件是否可以扫描,可扫描的文件有 JS、html、vue 等
      // 因为有可能裸依赖的入口是 css 等非 JS 模块的文件
      else if (isScannable(resolved)) {
        // 真实路径不在 node_modules 中,则证明是 monorepo,实际上代码还是在用户的目录中
        // 是用户自己写的代码,不应该 external
        return {
          path: path.resolve(resolved)
        }
      } else {
        // 其他模块不可扫描,直接忽略,external
        return {
            path,
            external: true
        }
      }
    } else {
      // 解析不到依赖,则记录缺少的依赖
      missing[id] = normalizePath(importer)
    }
  }
)
  • 如果文件在 node_modules 中,才认为是 bare import,记录当前模块
  • 文件不在 node_modules 中,则是 monorepo,是用户自己写的代码
  • 如果这些代码 isScanable 可扫描(即含有 JS 代码),则继续深入处理
  • 其他非 JS 模块,external

html 类型模块

如: index.htmlapp.vue

代码语言:typescript
AI代码解释
复制
const htmlTypesRE = /\.(html|vue|svelte|astro)$/

// html types: 提取 script 标签
build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => {
    // 将模块路径,转成文件的真实路径
    const resolved = await resolve(path, importer)
    if (!resolved) return
    
    // 不处理 node_modules 内的
    if (resolved.includes('node_modules'){
        return
   }

    return {
        path: resolved,
        // 标记 namespace 为 html 
        namespace: 'html'
    }
})

解析过程很简单,只是用于过滤掉一些不需要的模块,并且标记 namespace 为 html

真正的处理在加载阶段:

代码语言:typescript
AI代码解释
复制
// 正则,匹配例子: <script type=module></script>
const scriptModuleRE = /(<script\b[^>]*type\s*=\s*(?:"module"|'module')[^>]*>)(.*?)<\/script>/gims
// 正则,匹配例子: <script></script>
export const scriptRE = /(<script\b(?:\s[^>]*>|>))(.*?)<\/script>/gims

build.onLoad(
    { filter: htmlTypesRE, namespace: 'html' },
    async ({ path }) => {
        // 读取源码
        let raw = fs.readFileSync(path, 'utf-8')
        // 去掉注释,避免后面匹配到注释
        raw = raw.replace(commentRE, '<!---->')

        const isHtml = path.endsWith('.html')
        // scriptModuleRE: <script type=module></script>
        // scriptRE: <script></script>
        // html 模块,需要匹配 module 类型的 script,因为只有 module 类型的 script 才能使用 import
        const regex = isHtml ? scriptModuleRE : scriptRE

        // 重置正则表达式的索引位置,因为同一个正则表达式对象,每次匹配后,lastIndex 都会改变
        // regex 会被重复使用,每次都需要重置为 0,代表从第 0 个字符开始正则匹配
        regex.lastIndex = 0
        // load 钩子返回值,表示加载后的 js 代码
        let js = ''
        let scriptId = 0
        let match: RegExpExecArray | null

        // 匹配源码的 script 标签,用 while 循环,因为 html 可能有多个 script 标签
        while ((match = regex.exec(raw))) {
            // openTag: 它的值的例子: <script type="module" lang="ecmascript" src="xxx">
            // content: script 标签的内容
            const [, openTag, content] = match
            
            // 正则匹配出 openTag 中的 type 和 lang 属性
            const typeMatch = openTag.match(typeRE)
            const type =
                  typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3])
            const langMatch = openTag.match(langRE)
            const lang =
                  langMatch && (langMatch[1] || langMatch[2] || langMatch[3])
            
            // 跳过 type="application/ld+json" 和其他非 non-JS 类型
            if (
                type &&
                !(
                    type.includes('javascript') ||
                    type.includes('ecmascript') ||
                    type === 'module'
                )
            ) {
                continue
            }
            
            // esbuild load 钩子可以设置 应的 loader
            let loader: Loader = 'js'
            if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') {
                loader = lang
            } else if (path.endsWith('.astro')) {
                loader = 'ts'
            }
            
            // 正则匹配出 script src 属性
            const srcMatch = openTag.match(srcRE)
            // 有 src 属性,证明是外部 script
            if (srcMatch) {
                
                const src = srcMatch[1] || srcMatch[2] || srcMatch[3]
                // 外部 script,改为用 import 用引入外部 script
                js += `import ${JSON.stringify(src)}\n`
            } else if (content.trim()) {
                // 内联的 script,它的内容要做成虚拟模块

                // 缓存虚拟模块的内容
                // 一个 html 可能有多个 script,用 scriptId 区分
                const key = `${path}?id=${scriptId++}`
                scripts[key] = {
                    loader,
                    content,
                    pluginData: {
                        htmlType: { loader }
                    }
                }

                // 虚拟模块的路径,如 virtual-module:D:/project/index.html?id=0
                const virtualModulePath = virtualModulePrefix + key
                js += `export * from ${virtualModulePath}\n`
            }
        }

        return {
            loader: 'js',
            contents: js
        }
    }
)

加载阶段的主要做有以下流程:

  • 读取文件源码
  • 正则匹配出所有的 script 标签,并对每个 script 标签的内容进行处理
  • 外部 script,改为用 import 引入
  • 内联 script,改为引入虚拟模块,并将对应的虚拟模块的内容缓存到 script 对象。
  • 最后返回转换后的 js

srcMatch1 || srcMatch2 || srcMatch3 是干嘛?

我们来看看匹配的表达式:

代码语言:typescript
AI代码解释
复制
const srcRE = /\bsrc\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/im

因为 src 可以有以下三种写法:

  • src="xxx"
  • src='xxx'
  • src=xxx

三种情况会出现其中一种,因此是三个捕获组

虚拟模块是如何加载成对应的 script 代码的?

代码语言:typescript
AI代码解释
复制
export const virtualModuleRE = /^virtual-module:.*/

// 匹配所有的虚拟模块,namespace 标记为 script
build.onResolve({ filter: virtualModuleRE }, ({ path }) => {
  return {
    // 去掉 prefix
    // virtual-module:D:/project/index.html?id=0 => D:/project/index.html?id=0
    path: path.replace(virtualModulePrefix, ''),
    namespace: 'script'
  }
})

// 之前的内联 script 内容,保存到 script 对象,加载虚拟模块的时候取出来
build.onLoad({ filter: /.*/, namespace: 'script' }, ({ path }) => {
  return scripts[path]
})

虚拟模块的加载很简单,直接从 script 对象中,读取之前缓存起来的内容即可。

这样之后,我们就可以把 html 类型的模块,转换成 JS 了

扫描结果

下面是一个 depImport 对象的例子:

代码语言:typescript
AI代码解释
复制
{
  "vue": "D:/app/vite/node_modules/.pnpm/vue@3.2.37/node_modules/vue/dist/vue.runtime.esm-bundler.js",
  "vue/dist/vue.d.ts": "D:/app/vite/node_modules/.pnpm/vue@3.2.37/node_modules/vue/dist/vue.d.ts",
  "lodash-es": "D:/app/vite/node_modules/.pnpm/lodash-es@4.17.21/node_modules/lodash-es/lodash.js"
}
  • key:模块名称
  • value:模块的真实路径

总结

依赖扫描是预构建前的一个非常重要的步骤,这决定了 Vite 需要对哪些依赖进行预构建。

本文介绍了 Vite 会对哪些内容进行依赖预构建,然后分析了实现依赖扫描的基本思路 —— 深度遍历依赖树,并对各种类型的模块进行处理。然后介绍了 Vite 如何巧妙的使用 esbuild 实现这一过程。最后对这部分的源码进行了解析:

  • 最复杂的就是 html 类型模块的处理,需要使用虚拟模块
  • 当遇到 bare import 时,需要判断是否在 node_modules 中,在的才记录依赖,然后 external。
  • 其他 JS 无关的模块就直接 external
  • JS 模块由于 esbuild 本身能处理,不需要做任何的特殊操作

最后获取到的 depImport 是一个记录依赖以及其真实路径的对象

扩展阅读

最后

如果这篇文章对您有所帮助,请帮忙点个赞👍,您的鼓励是我创作路上的最大的动力。

最近注册了一个公众号,刚刚起步,名字叫:Candy 的修仙秘籍,欢迎大家关注~

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

评论
登录后参与评论
暂无评论
推荐阅读
445 亿元的存储市场:戴尔 120亿、HPE/H3C 49亿、NetApp 44亿、华为 40亿、日立 22亿、IBM 21亿
2021年9月10日,IDC发布的《全球企业存储系统季度跟踪报告》显示: 2021 年第二季度企业外部OEM存储系统的全球市场收入同比增长9.7%至69亿美元(444.77亿人民币); 该季度外部OEM存储容量的总出货量同比猛增27.9%至22.1艾字节(EB)。 2021年第二季度,以超大规模数据中心为直接销售对象的原始设计制造商(ODM)这个群体产生的收入同比下降7.7%至64亿美元,而出货量同比增长13.8%至88.7 EB。整个市场(外部OEM+ ODM Direct+基于服务器的存储)的企业存储容
云头条
2022/03/18
1.4K0
洗牌!2019年全闪存市场前五都有谁?
Gartner第一份数据中心半导体收入报告显示,数据中心服务器和SSD的收入预计将在2019年至2024年之间猛增。与此同时,传统机械硬盘在数据中心收入将持续收缩。
大数据在线
2020/05/20
7220
高端存储30年,谁主沉浮?
近期在业界谈得火热的“第五代存储”这一概念,再次让智能时代里的高端存储成为广泛热议的话题。究竟什么是高端存储?它给智能时代带来何种影响和启发?《智能时代下的高端存储》系列连载将从定义、历史、技术、行业应用几大板块带来深度解析!
IT创事记
2022/08/30
6280
高端存储30年,谁主沉浮?
HPE有意收购Nutanix
据彭博社报道,HPE已经表示有兴趣收购IT基础设施软件公司Nutanix,Nutanix的股价受此消息影响上涨超过8%。
静一
2022/12/08
5210
「存储传记」从“屌丝”到王者,EMC的逆袭之路
哈哈,没错,我们处在信息时代,已经离不开视频、音乐、图片、文本、表格这样的数据的存储。
了不起的云计算
2021/02/02
1.3K0
2020年超融合市场:13家HCI供应商将获得青睐
2020年超融合增长势头不减,在新的一年中,有几家HCI(超融合基础设施)供应商值得关注。本文研究了几种不同类别的供应商、他们工具的特性、处理的用例以及是什么使他们的产品在竞争中脱颖而出。
SDNLAB
2020/02/14
1.1K0
2020年超融合市场:13家HCI供应商将获得青睐
全球全闪存阵列市场最新数据:这家中国公司的增长吓死你!
前几天IDC公布了最新一期全球企业存储系统季度追踪报告。报告中透露全闪存阵列市场2018年第三季度市场规模约为21.5亿美元,同比大幅增长39.3%。近日,IDC和富国银行证券给出了本季度全闪存阵列市场更加详细的数据。
大数据在线
2019/07/08
6340
全球全闪存阵列市场最新数据:这家中国公司的增长吓死你!
2018 Q4全球服务器存储市场:巨头复苏,ODM终于降了!
IDC公布了2018年第四季度全球服务器、存储市场追踪报告。从两份报告数据来看,本季度市场都出现了较高的增长,服务器市场营收还创造了最新的历史新高;此外,以DELL、HPE、NetApp为代表的传统厂商在本季度全面复苏,大幅领先市场平均增长,而ODM们的增长速度终于降下来。
大数据在线
2019/07/08
1.6K0
2018 Q4全球服务器存储市场:巨头复苏,ODM终于降了!
分布式存储会一统存储市场吗 ?
近年来,随着云、大数据、AI、区块链等技术的发展,分布式架构在IT市场持续火热,在存储领域,分布式存储蓬勃发展。 其中在AI应用最火热的汽车自动驾驶研发领域,每个车企都需要对数百PB数据进行采集、存储、分析训练、仿真。 根据预测,到2025年全球数据将增长到175ZB,其中非结构化数据占比将超过80%,分布式存储凭借高扩展性和易管理能力,成为承载海量数据的重要选择。同时,在政府、运营商、金融等大规模云化数据中心,各大云厂商、分布式存储厂商都在积极推动分布式存储更广泛地应用,替代部分传统存储阵列。 种种迹象
云头条
2022/03/18
6460
2022年Q2,三大云厂商整体营收集体下滑
AWS、Azure 和谷歌云陷入了一场长期的斗争。 AWS 是云计算领域的先驱,数十年长期处于领导地位。 微软作为世界上最大的软件公司之一,正在忙于将其庞大的客户群转移到 Azure 云。 谷歌进入云计算市场比较晚,但在 2018 年底换了Thomas Kurian担任新任CEO后,这几年表现还算可圈可点。 近日,三巨头都发布了第二季度财务报告,结果不用多说,先看图。 三大云巨头很有默契地将整体销售下滑归咎于云采用的增长放缓,疫情带来的需求激增已经趋于平稳。尽管如此,三者依旧坚持继续大力投资云计算,建新的
SDNLAB
2022/08/26
8440
2022年Q2,三大云厂商整体营收集体下滑
VMware 与 Dell 离婚已完成:VMware 市值 638 亿美元、Dell 853 亿美元
VMware Inc.在完成从前母公司戴尔科技公司拆分出来的工作后,今天成为了一家独立公司,此举已进行了好几个季度。按照最近股价计算,戴尔剩余的硬件业务其隐含价值为330亿美元。 截至今天上午,VMware 的市值超过630亿美元,是数据中心和云基础设施管理软件的头部供应商。该公司成立于1998年,六年后被数据存储制造商EMC以6.25亿美元的价格收购。戴尔在2016年收购EMC后,VMware随之成为戴尔的一部分。 戴尔当初为收购EMC支付了670亿美元。这笔交易是科技行业迄今为止数额最大的一笔收
云头条
2022/03/18
2.2K0
IBM Storwize存储品牌退出历史舞台
IBM Storwize存储品牌将退出历史舞台,通用阵列(Storwize和FlashSystem)将整合到FlashSystem品牌之下。
大数据在线
2020/02/25
8820
GitLab上市首日大涨35%,市值逼近千亿,营收87%来自订阅服务
GitLab最终将其股价定价为每股77美元,超出此前透露的发行价区间每股66-69美元。
量子位
2021/10/20
7550
合作变合并?HPE或将收购Nutanix
彭博社周四报道称,HPE正在就收购云计算公司 Nutanix进行谈判。HPE专注于战略收购,以加速业务增长、填补产品组合空白并加强其竞争地位。
SDNLAB
2022/12/14
9610
合作变合并?HPE或将收购Nutanix
第一大云厂商营收 3956 亿元、营业利润 1179 亿元
去年互联网商务和云业务的增长共同推动亚马逊的利润达到了333.64亿美元,比前一年的213.31亿美元增长逾56%。 亚马逊去年的销售额为4698.22亿美元,比2020年全年的3860.64亿美元增长21.7%。 AWS的收入更是增长了37%,全年收入达到622.02亿美元(3956 亿人民币),俨然是市场的领头羊。 全年营业利润为185.32亿美元(1179亿人民币),比前一年增长37%。 第四季度收入猛增40%,达到177.8亿美元(1131亿人民币)。 相比之下,亚马逊的零售业务却报告经营亏损1
云头条
2022/03/18
2590
服务器衰退之前的最后一次欢呼
编者按:对于服务器提供商而言,可能面临十年来的一次灾难。由于冠状病毒的爆发以及全球各个经济体的结构性问题,目前经济衰退不再是单个国家的事情,在未来几个季度甚至几年中都是全球关注的热点。
SDNLAB
2020/03/24
6420
服务器衰退之前的最后一次欢呼
Dell:AI洪流下,被忽视的企业级IT巨头
戴尔,这家由创始人迈克尔·戴尔领导的科技巨头,或许不像一些新兴公司那样光芒四射,但它总能巧妙地驾驭科技浪潮。在当前汹涌澎湃的人工智能时代,戴尔正面临一个核心挑战:如何抓住爆炸性的AI机遇,同时将其已部署在全球数百万客户现场的服务器、存储和PC平稳过渡到新时代?面对庞大的现有客户群和以硬件为主的业务模式,戴尔能否成功将传统优势转化为AI时代的增长引擎?它将如何构建“AI工厂”,利用强大的渠道,并实现核心基础设施的现代化?本文将深入解析戴尔在AI浪潮中的战略布局、市场表现以及面临的挑战,带你了解这家巨头如何在变革中稳固前行。
数据存储前沿技术
2025/05/22
1140
Dell:AI洪流下,被忽视的企业级IT巨头
靠Kafka挣不了钱了?Confluent 股价一夜暴跌超 44%!
截至美国东部时间周四中午 12:30,Confluent(纳斯达克股票交易代码:CFLT)的股价下跌了 44.3%。在这次大幅下跌之前,Confluent 股价今年以来已经上涨了 26%。
深度学习与Python
2023/11/06
1610
靠Kafka挣不了钱了?Confluent 股价一夜暴跌超 44%!
Meta股价暴跌26%气哭小扎!市值蒸发2000亿,元宇宙没搞到钱?
Facebook母公司Meta,美国当地时间周三发布的季报揭示了一个惊人的统计数据:该公司的增长有史以来第一次在全球范围内停滞不前。
新智元
2022/02/24
2900
Meta股价暴跌26%气哭小扎!市值蒸发2000亿,元宇宙没搞到钱?
Pure3.7亿美元收购Portworx,容器存储再成焦点
根据外电消息,Pure Storage宣布将以3.7亿美元收购数据服务平台Portworx。此举将让Pure Storage在容器存储相关领域得到补强。据悉,本次交易预计在本月底完成。
大数据在线
2020/09/27
6080
推荐阅读
相关推荐
445 亿元的存储市场:戴尔 120亿、HPE/H3C 49亿、NetApp 44亿、华为 40亿、日立 22亿、IBM 21亿
更多 >
LV.0
这个人很懒,什么都没有留下~
目录
  • 依赖预构建的内容
  • 依赖扫描
    • 实现思路
    • 具体实现
      • html 类型的模块处理
  • 源码解析
    • 依赖扫描的入口
    • esbuild 插件
    • 扫描插件的实现
      • JS 模块
      • 其他 JS 无关的模块
      • bare import
      • html 类型模块
    • 扫描结果
  • 总结
  • 扩展阅读
  • 最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档