前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手摸手打造类码上掘金在线IDE(六)——沙箱编译(二)

手摸手打造类码上掘金在线IDE(六)——沙箱编译(二)

作者头像
用户7413032
发布2022-12-02 16:49:32
7590
发布2022-12-02 16:49:32
举报
文章被收录于专栏:佛曰不可说丶

前言

我们上回书说道沙箱编译的vue编译部分,很多jym以为我会就此金盆洗手, 等着东家发完盒饭踏实回家搬砖。

甚至有Jy 略带嘲讽的给我评论道:

我能从他们的字里行间体会到他们在质问我,就这?我那啥都那啥了你就给我看这?

而由于行文是从丘处机路过牛家村开始,略显墨迹,阅读量,点赞量,可谓惨不忍睹。

发生这种情况,我以为有三个原因

  • 1、本身沙箱编译内容不是流量密码,不是真正干过这个的人,很难产生兴趣和好奇心!
  • 2、我这篇小作文写的确实枯燥,既没有讲原理,也没有讲心得,而是讲科普。
  • 3、对于他们的日常开发没有任何帮助

亲爱的jym啊,我怎么会让自己晚节不保呢?我怎么能让自己这么没有深度呢?

当然还有后续啊,今天我们就来讲讲原理,毕竟原理才是技术圈的流量密码

本着帮人帮到底 送佛送到西的优良品质,也本着绝不认输,不点赞不断更的态度(主要是一个点赞都没有脸上实在挂不住了)。

我们今天就来细致的讲一下vue模板 在到底是如何编译的。

也能让大家能理解,vue项目的整个编译流程,这样就能在工作中更好的学以致用,这样也能在面试官的面前游刃有余

废话少说,我们正式开始!

当然国际惯例,讲编译原理之前,我们还是要从丘处机路过牛家村开始

正常的vue模板编译流程

在介绍正常的vue模板编译流程,我们需要一些前置支持,我们知道的代码编译分为两种

  • 1、html内容直接编译
  • 2、sfc单文件组件编译

html内容编译

html 的编译其实就非常简单了说白了就是利用全量vue 版本,拿到html的字符串进行编译即可

举个例子:

代码语言:javascript
复制
<head>
    <script src="./vue.global.js"></script>
</head>
<div id="app">
    <div>
        {{message}}
    </div>
</div>
<script>
    const app = Vue.createApp({
        setup() {
            const { ref } = Vue
            const message = ref('hello world')
            return {
                message
            }
        }
    })
    app.mount('#app')
</script>Ï
<body>
</body>

以上代码他最后就会在vue.global.js的加持下解析 idapp的字符串模板

这也是vue能够在行业内屹立不倒的原因,小而美,上手简单,开箱即用。

而反观react,相信干过的都知道,你想要使用他的语法,光引入一个js 文件那是远远不够的!

而他的实现原理也非常简单,仅仅在初始化的时候将模板内容拿到,然后调用 中的@vue/compiler 执行编译即可!

由于我们这期编译原理为主,运行时我们暂时按下不表

我们来看源代码:

代码语言:javascript
复制
import { compile } from '@vue/compiler-dom'
// 初始化编译函数
function compileToFunction(
  template: string | HTMLElement,
  options?: CompilerOptions
): RenderFunction {
  // 判断了是否是字符串,因为在初始化的时候,可以使用字符
  if (!isString(template)) {
    if (template.nodeType) {
      template = template.innerHTML
    } else {
      __DEV__ &amp;&amp; warn(`invalid template option: `, template)
      return NOOP
    }
  }

  const key = template
  // 作者还机制的使用了缓存,如果已经编译过了,就直接返回
  const cached = compileCache[key]
  if (cached) {
    return cached
  }
  // 开始根据id拿到模板
  if (template[0] === '#') {
    const el = document.querySelector(template)
    if (__DEV__ &amp;&amp; !el) {
      warn(`Template element not found or is empty: ${template}`)
    }
    // 此处已经拿到模板了
    template = el ? el.innerHTML : ``
  }

  // 拿到编译后的代码
  const { code } = compile(template)

  const render = (
    __GLOBAL__ ? new Function(code)() : new Function('Vue', code)(runtimeDom)
  ) as RenderFunction
  // 生成render 函数
  return (compileCache[key] = render)
}

以上vue3的源码中,我们就能清楚的看出来,是在初始化的时候,引用模板编译模块,来生成render 函数。

这个render函数相信大家都不陌生,毕竟面试常考,我也就不再赘述

接下来,就是sfc单文件组件编译

sfc单文件组件编译

上期我们说过sfc单文件组件,他从本质上来说,就是只适用于vue的一种规范,既然,是适用于vue规范,那么必然不行业公认的,于是他就需要转义,给他变成浏览器能跑起来的代码

而编译sfc单文件组件,就需要node环境,因为node 能做文件io操作!

使用上其实很简单,我们利用node读取vue单文件组件,然后将其中内容,分开编译输出,打包为浏览器可以运行的代码!

然而,在这个前端纷繁复杂生态繁荣的年代!我们干事情千万不要从0开始,我们要从1到10,我们要站在巨人的肩膀上!

众所周知,在前端基建领域的巨人,非webpack莫属!

他就能实现我们要做的所有事情,我们只需要付出少量心血写个插件即可

于是Vue Loader诞生了!

Vue Loader

Vue Loader 在上回书,我们也说道过, 是一个 webpack 的 loader,它允许你以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件

简而言之,Vue Loader 在webpack的基础上建立了灵活且极其强大的前端工作流,来帮助撰你写 Vue.js 应用。

他的使用方式非常简单,在webpack中配置即可

代码语言:javascript
复制
// webpack.config.js
const VueLoaderPlugin = require('vue-loader/lib/plugin')

module.exports = {
  module: {
    rules: [
      // ... 其它规则
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
  },
  plugins: [
  
     // 这个插件使 .vue 中的各类语言块匹配相应的规则
    new VueLoaderPlugin()
  ]
}

而在开箱即用的vue-cli中直接内置了,我么你甚至都不需要引用!下载相关脚手架即可开始开发!

webpack 中的Vue Loader插件到底做了什么事情呢?

一图胜千言,但还是简单的说一下吧!

总体上来说,Vue Loader 对于.vue文件的处理分为那么几步关键点:

  • 1、通过 parse方法 生成 descriptor描述文件,描述符中包含了vue解析后的各个结果,比如template、style、script
  • 2、处理过后的type 区别并缓存内容提高编译性能
  • 3、配合compiler-sfc生成 code代码

如上图所示,我们可以简单的看下他编译后的代码!

那么接下下来我们来探究一下vue-loader的原理了,细致的探究一下他是怎么实现的。

代码语言:javascript
复制
// 默认导出的loader函数注意loader本质上就是个函数
export default function loader(
  this: webpack.loader.LoaderContext,// webpack的loader上下文
  source: string// 源码
) {
  const loaderContext = this
  //拿到上下文中的相关内容
  const {
    mode,
    target,
    sourceMap,
    rootContext,
    resourcePath,
    resourceQuery: _resourceQuery = '',
  } = loaderContext
  //一些前置内容的处理,比如loaderUtils获取配置对象,传入参数处理等,不是我们本次关心的重点
  const rawQuery = _resourceQuery.slice(1)
  const incomingQuery = qs.parse(rawQuery)
  const resourceQuery = rawQuery ? `&amp;${rawQuery}` : ''
  const options = (loaderUtils.getOptions(loaderContext) ||
    {}) as VueLoaderOptions

  const isServer = options.isServerBuild ?? target === 'node'
  const isProduction =
    mode === 'production' || process.env.NODE_ENV === 'production'

  const filename = resourcePath.replace(/\?.*$/, '')
  // 通过vue/compiler-sfc 分离内容
  const { descriptor, errors } = parse(source, {
    filename,
    sourceMap,
  })

  const asCustomElement =
    typeof options.customElement === 'boolean'
      ? options.customElement
      : (options.customElement || /\.ce\.vue$/).test(filename)

  // 缓存当前编译内容,防止下次编译
  setDescriptor(filename, descriptor)


  // 作用域CSS和热重载的处理,生成唯一id
  const rawShortFilePath = path
    .relative(rootContext || process.cwd(), filename)
    .replace(/^(\.\.[\/\\])+/, '')
  const shortFilePath = rawShortFilePath.replace(/\\/g, '/')
  const id = hash(
    isProduction
      ? shortFilePath + '\n' + source.replace(/\r\n/g, '\n')
      : shortFilePath
  )
  //vue-loader 推导策略
  // 这里主要就是通过vue插件来处理编译分离后的内容
  // 主要就是生成引用的js、render函数,css等内容
  //比如'?vue&amp;type=script&amp;lang=js' 就会走js 的处理逻辑
  // 分别通过插件styleInlineLoader,stylePostLoader。templateLoader 来处理
  if (incomingQuery.type) {
    return selectBlock(
      descriptor,
      id,
      options,
      loaderContext,
      incomingQuery,
      !!options.appendExtension
    )
  }

  // 前置处理css scoped 
  const hasScoped = descriptor.styles.some((s) => s.scoped)
  const needsHotReload =
    !isServer &amp;&amp;
    !isProduction &amp;&amp;
    !!(descriptor.script || descriptor.scriptSetup || descriptor.template) &amp;&amp;
    options.hotReload !== false

  const propsToAttach: [string, string][] = []

  // 处理script
  let scriptImport = `const script = {}`
  let isTS = false
  const { script, scriptSetup } = descriptor
  if (script || scriptSetup) {
    const lang = script?.lang || scriptSetup?.lang
    isTS = !!(lang &amp;&amp; /tsx?/.test(lang))
    const src = (script &amp;&amp; !scriptSetup &amp;&amp; script.src) || resourcePath
    const attrsQuery = attrsToQuery((scriptSetup || script)!.attrs, 'js')
    //拼接下次请求的query
    const query = `?vue&amp;type=script${attrsQuery}${resourceQuery}`
    const scriptRequest = stringifyRequest(src + query)
    // 生成代码
    scriptImport =
      `import script from ${scriptRequest}\n` +
      // support named exports
      `export * from ${scriptRequest}`
  }

  // 处理模板template
  let templateImport = ``
  let templateRequest
  const renderFnName = isServer ? `ssrRender` : `render`
  const useInlineTemplate = canInlineTemplate(descriptor, isProduction)
  if (descriptor.template &amp;&amp; !useInlineTemplate) {
    const src = descriptor.template.src || resourcePath
    const idQuery = `&amp;id=${id}`
    const scopedQuery = hasScoped ? `&amp;scoped=true` : ``
    const attrsQuery = attrsToQuery(descriptor.template.attrs)
    const tsQuery =
      options.enableTsInTemplate !== false &amp;&amp; isTS ? `&amp;ts=true` : ``
    // 同样的处理模板内容
    const query = `?vue&amp;type=template${idQuery}${scopedQuery}${tsQuery}${attrsQuery}${resourceQuery}`
    templateRequest = stringifyRequest(src + query)
    // 生成代码 
    templateImport = `import { ${renderFnName} } from ${templateRequest}`
    propsToAttach.push([renderFnName, renderFnName])
  }

  // 处理styles内容
  let stylesCode = ``
  let hasCSSModules = false
  const nonWhitespaceRE = /\S+/
  if (descriptor.styles.length) {
    descriptor.styles
      .filter((style) => style.src || nonWhitespaceRE.test(style.content))
      .forEach((style, i) => {
        const src = style.src || resourcePath
        const attrsQuery = attrsToQuery(style.attrs, 'css')
        const idQuery = !style.src || style.scoped ? `&amp;id=${id}` : ``
        const inlineQuery = asCustomElement ? `&amp;inline` : ``
        const query = `?vue&amp;type=style&amp;index=${i}${idQuery}${inlineQuery}${attrsQuery}${resourceQuery}`
        const styleRequest = stringifyRequest(src + query)
        if (style.module) {
          if (asCustomElement) {
            loaderContext.emitError(
              `<style module> is not supported in custom element mode.`
            )
          }
          if (!hasCSSModules) {
            stylesCode += `\nconst cssModules = {}`
            propsToAttach.push([`__cssModules`, `cssModules`])
            hasCSSModules = true
          }
          // 如果有热更新,拼接添加css 代码 添加热更新等内容
          stylesCode += genCSSModulesCode(
            id,
            i,
            styleRequest,
            style.module,
            needsHotReload
          )
        } else {
          // 否则直接拼接
          if (asCustomElement) {
            stylesCode += `\nimport _style_${i} from ${styleRequest}`
          } else {
            stylesCode += `\nimport ${styleRequest}`
          }
        }
        // TODO SSR critical CSS collection
      })
  }

  let code = [templateImport, scriptImport, stylesCode]
    .filter(Boolean)
    .join('\n')

  // attach scope Id for runtime use
  if (hasScoped) {
    propsToAttach.push([`__scopeId`, `"data-v-${id}"`])
  }

  // 拼接处最后的代码段
  if (!propsToAttach.length) {
    code += `\n\nconst __exports__ = script;`
  } else {
    code += `\n\nimport exportComponent from ${exportHelperPath}`
    code += `\nconst __exports__ = /*#__PURE__*/exportComponent(script, [${propsToAttach
      .map(([key, val]) => `['${key}',${val}]`)
      .join(',')}])`
  }

  //生成代码最终返回
  code += `\n\nexport default __exports__`
  return code
}

他的步骤其实本质上其实就是在开发环境下来拼接生成esmodule代码, 然后代码就会拼接成如下这样:

当然这只是第一步,因为你发现他又引入了单独拆分后的文件。 接下来,我们就要对每个单独拆分后的类型文件做处理,此时的处理就要依赖于vue-loader这个包中的一个webpack插件来做下一步。

VueLoaderPlugin

VueLoaderPlugin,他的源代码非常简单,主要就是兼容了webpack4webpack5

代码语言:javascript
复制
import webpack = require('webpack')
declare class VueLoaderPlugin implements webpack.Plugin {
  static NS: string
  apply(compiler: webpack.Compiler): void
}

let Plugin: typeof VueLoaderPlugin
// 兼容webpack4和webpack5
if (webpack.version &amp;&amp; webpack.version[0] > '4') {

  Plugin = require('./pluginWebpack5').default
} else {

  Plugin = require('./pluginWebpack4').default
}

export default Plugin

接下来,我们就以webpack5为例讲讲这个插件怎么处理剩余的内容。

至于为啥将webpack5,就跟买东西一样啊,买新不加旧!Ï众所周知,webpack插件本质上是个class类

那我们只需要看看这个类里面干了什么事情即可

pluginWebpack5

我们之前说了 pluginWebpack5本质上是个类,这个类由于能拿到webpack编译的参数,于是,他便可以动态的改变他的配置对象,从而注入新的loader来实现拆分后文件的解析,这也是我们引入插件后就能解析内容的原理

代码如下:

代码语言:javascript
复制
class VueLoaderPlugin implements Plugin {
  static NS = NS

  apply(compiler: Compiler) {
    //拿到编译之后的一些模块
    const normalModule = compiler.webpack.NormalModule || NormalModule

    //相当于做一些出错误处理,日志输出啥的
    compiler.hooks.compilation.tap(id, (compilation) => {
      normalModule
        .getCompilationHooks(compilation)
        .loader.tap(id, (loaderContext: any) => {
          loaderContext[NS] = true
        })
    })
    // 此处省略无关紧要的一些代码
    //...
    //...
    // 开始注册编译模板loader
    const templateCompilerRule = {
      loader: require.resolve('./templateLoader'),
      resourceQuery: (query?: string) => {
        if (!query) {
          return false
        }
        const parsed = qs.parse(query.slice(1))
        return parsed.vue != null &amp;&amp; parsed.type === 'template'
      },
      options: vueLoaderOptions,
    }


    //pitcher注册除了模板之外的剩余内容
    const pitcher = {
      loader: require.resolve('./pitcher'),
      resourceQuery: (query?: string) => {
      
        if (!query) {
          return false
        } 
        // 解析 query 上带有 vue 标识的资源
        const parsed = qs.parse(query.slice(1))
        return parsed.vue != null
      },
    }

    // 重写loader规则以便能够解析vue文件剩余内容
    compiler.options.module!.rules = [
      pitcher,
      templateCompilerRule,
       ...rules,
    ]
  }
}

以上简写代码中,我们能很清楚的看到,他重写了rules 也就是之前那个webpack的配置表

接下来就水到渠成了,由于vue-loader返回了拼接后的文件,那么他就会去处理拼接后的文件,也就是我们前面那张截图

然后就会根据正则规则触发那两个新的loader 从而实现编译

接下来我们也来简单介绍一下这两个loader

templateLoader

代码语言:javascript
复制
// 模板的处理其实就是调用vue/compiler-sfc的compileTemplate方法
const TemplateLoader: webpack.loader.Loader = function (source, inMap) {
  source = String(source)
  const loaderContext = this
  // 前置处理
  const options = (loaderUtils.getOptions(loaderContext) ||
    {}) as VueLoaderOptions

  const isServer = options.isServerBuild ?? loaderContext.target === 'node'
  const isProd =
    loaderContext.mode === 'production' || process.env.NODE_ENV === 'production'
  const query = qs.parse(loaderContext.resourceQuery.slice(1))
  const scopeId = query.id as string
  const descriptor = getDescriptor(loaderContext.resourcePath)
  const script = resolveScript(
    descriptor,
    query.id as string,
    options,
    loaderContext
  )

  let templateCompiler: TemplateCompiler | undefined
  if (typeof options.compiler === 'string') {
    templateCompiler = require(options.compiler)
  } else {
    templateCompiler = options.compiler
  }
  // 主要就是这里,调用vue/compiler-sfc的compileTemplate方法
  const compiled = compileTemplate({
    source,
    filename: loaderContext.resourcePath,
    inMap,
    id: scopeId,
    scoped: !!query.scoped,
    slotted: descriptor.slotted,
    isProd,
    ssr: isServer,
    ssrCssVars: descriptor.cssVars,
    compiler: templateCompiler,
    compilerOptions: {
      ...options.compilerOptions,
      scopeId: query.scoped ? `data-v-${scopeId}` : undefined,
      bindingMetadata: script ? script.bindings : undefined,
      ...resolveTemplateTSOptions(descriptor, options),
    },
    transformAssetUrls: options.transformAssetUrls || true,
  })

  // tips
  if (compiled.tips.length) {
    compiled.tips.forEach((tip) => {
      loaderContext.emitWarning(tip)
    })
  }
  // 返回结果,让下一个loader处理
  const { code, map } = compiled
  loaderContext.callback(null, code, map)
}

而编译后的结果在babelsourceMap的加持下变成了这样

我们可以很清楚的看到render函数

pitcher

pitcher本质上就是处理除了模板以外的情况

代码语言:javascript
复制
// 处理css 内容,js 内容可以用babel 处理
const stylePostLoaderPath = require.resolve('./stylePostLoader')
const styleInlineLoaderPath = require.resolve('./styleInlineLoader')
// pitcher-loader 是个空壳子
const pitcher: webpack.loader.Loader = (code) => code
// pitcher这个loader 中的 pitch 方法才是真正的pitcher
//loader 总是从右到左被调用。有些情况下,loader 只关心 request 后面的 元数据(metadata),
//并且忽略前一个 loader 的结果。在实际(从右到左)执行 loader 之前,会先从左到右调用 loader 上的 pitch 方法。
export const pitch = function () {
  const context = this as webpack.loader.LoaderContext
  const rawLoaders = context.loaders.filter(isNotPitcher)
  let loaders = rawLoaders

  if (loaders.some(isNullLoader)) {
    return
  }
  // 接受参数
  const query = qs.parse(context.resourceQuery.slice(1))
  // 省略无用代码
  //......
  // 处理css 内容
  if (query.type === `style`) {
    const cssLoaderIndex = loaders.findIndex(isCSSLoader)
    if (cssLoaderIndex > -1) {
      const afterLoaders =
        query.inline != null
          ? [styleInlineLoaderPath]
          : loaders.slice(0, cssLoaderIndex + 1)
      const beforeLoaders = loaders.slice(cssLoaderIndex + 1)
      return genProxyModule(
        [...afterLoaders, stylePostLoaderPath, ...beforeLoaders],
        context,
        !!query.module || query.inline != null
      )
    }
  }
  // 处理其他情况,最后将生成代码再次放到下一个loader 中处理
  return genProxyModule(loaders, context, query.type !== 'template')
}

到这我们就能很清楚的理解他loader对于整个vue文件的解析了。

总体上来说,他就是解析了内容之后,生成目标代码,再通过,别的loader 去解析处理,最终形成浏览器可以使用的代码

好了,说到这,我们整个vue模板在node端,和weback 的加持下,算是解析完成了。

接下来就轮到我们的沙箱了

沙箱中的vue 编译流程

在我们的浏览器中,由于没有io操作,以及webpack的加持,我们将这笨重的webpack移植到浏览器上,略显费劲。

于是在大佬们的不断探索下,他们换了个思路,我们可以在浏览器端实现一个类似loader的东西,来转换代码不就行了吗。

node vue-loader不就是干这个用的吗?

在开始之前我们可以从结果以及目的,来倒推过程和写法!

我们知道,我们的目的,就是将一个vue模板代码 变成浏览器可执行代码然后通过eval 来执行?

那我们构造一个可以通过eval执行的函数不就可以了吗

以上代码其实就是我们要构建的结果,只不过和node环境不同的是,我们需要生成一个函数来整体执行。

这样一来,我们就能确定我们生成代码需要什么基础设施了babel@vue/compiler-sfcscss预处理器即可

这样一来他的原理就呼之欲出了,我们只需要有相应的实现然后执行即可。

上回书只说到了vue3的编译内容,如有兴趣传送门在此

这一回,我们雨露均沾

我们先说怎么编译vue模板

说起编译vue模板 我们还是仿照上一部分的步骤

  • 1、loader处理
  • 2、@vue/compiler-sfc 编译
  • 3、babel 编译,处理css
  • 4、eval执行

loader处理

这一块其实很简单,我们只需要拿到模板code 代码然后调用loader处理即可!

代码语言:javascript
复制
   // 有个函数相当于rules 匹配文件名,模仿webpack配置
    mapTransformers(module: Module): Array<[string, any]> {
        // 碰见js文件,就用babel转换
        if (/^(?!\/node_modules\/).*\.(((m|c)?jsx?)|tsx)$/.test(module.filepath)) {
            return [
                [
                    'babel-transformer',
                    {
                        presets: ['solid'],
                        plugins: ['solid-refresh/babel'],
                    },
                ],
            ];
        }

        if (/\.css$/.test(module.filepath)) {
            return [
                ['css-transformer', {}],
                ['style-transformer', {}],
            ];
        }
        //碰见vue文件,就用vue3-loader
        if (/\.vue$/.test(module.filepath)) {
            return [
                ['vue3-transformer', {}],
            ];
        }
        //碰见图片,就用url-loader
        if (/\.(png|jpeg|svg)$/.test(module.filepath)) {
            return [
                ['url-transformer', {}],
            ];
        }
        throw new Error(`No transformer for ${module.filepath}`);
    }

@vue/compiler-sfc 编译

这一步我们在上回书说道,如有兴趣传送门在此,我们不再赘述!

babel 编译,处理css

由于 compiler-sfc处理之后,是esmodule内容,所以我们还需要用在浏览器端的babel做一层转换

代码如下:

代码语言:javascript
复制
import * as babel from '@babel/standalone';
//使用babel 编译代码
export async function babelTransform({ code, filepath, config }: ITransformData): Promise<any> {
    const requires: Set<string> = new Set();
    const presets = await getPresets(config?.presets ?? []);
    const plugins = await getPlugins(config?.plugins ?? []);
    plugins.push(collectDependencies(requires));
    // 传入一些配置,进行babel 转义
  
    const transformed = babel.transform(code, {
        filename: filepath,
        presets,
        plugins,

        ast: false,
        sourceMaps: 'inline',
        compact: /node_modules/.test(filepath),
    });
   
    if (!transformed.code) {
        transformed.code = 'module.exports = {};';
    }
    // 返回编译结果,并且拿到依赖包名字
    return Promise.resolve({
        code: transformed.code,
        dependencies: requires,
    })
}

注意这里的babel是浏览器端专用的,大家可以去babel官网自行翻阅!

scss的代码处理,自不用多说,在上回书也说道了,只需要用sass.js这个包即可

eval执行

拿到编译后的代码之后,我们就可以执行代码了!

代码语言:javascript
复制
export default function (
  code: string,
  require: Function,
  context: { id: string; exports: any; hot?: any },
  env: Object = {},
  globals: Object = {}
) {
  const global = g;
  const process = {
    env: {
      NODE_ENV: 'development',
    },
  }; // buildProcess(env);
  // @ts-ignore
  g.global = global;
  // 构建函数中使用的变量!
  // 这里需要注意的是,我们之所以需要构建变量,是为了模拟nodejs中的require等方法,
  //因为babel 
  //所以我们需要在浏览器端模拟这些方法,来使程序跑起来
  const allGlobals: { [key: string]: any } = {
    require,
    module: context,
    exports: context.exports,
    process,
    global,
    swcHelpers,
    ...globals,
  };

  if (hasGlobalDeclaration.test(code)) {
    delete allGlobals.global;
  }

  const allGlobalKeys = Object.keys(allGlobals);
  const globalsCode = allGlobalKeys.length ? allGlobalKeys.join(', ') : '';
  const globalsValues = allGlobalKeys.map((k) => allGlobals[k]);
  try {
    // 构建函数
    const newCode = `(function $csb$eval(` + globalsCode + `) {` + code + `\n})`;

    // 执行函数
    (0, eval)(newCode).apply(allGlobals.global, globalsValues);

    return context.exports;
  } catch (err) {
    logger.error(err);
    logger.error(code);

    let error = err;
    if (typeof err === 'string') {
      error = new Error(err);
    }
    // @ts-ignore
    error.isEvalError = true;

    throw error;
  }
}

最后

ok,到这里,我们就算是基本的讲了一个在线IDE的沙箱编译的基本原理流程,当然,整个项目要想跑起来,需要的知识点还有很多,篇幅有限,我们今天先到这里!

后续如果还有下回书,我们继续讲react的编译,以及怎样内置依赖包等能力!

JYM支持,让咱们还有下一回,今天这一回咱们打完收工,领盒饭去了!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-11-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 正常的vue模板编译流程
    • html内容编译
      • sfc单文件组件编译
        • Vue Loader
          • 总体上来说,Vue Loader 对于.vue文件的处理分为那么几步关键点:
            • VueLoaderPlugin
              • pluginWebpack5
              • templateLoader
            • pitcher
            • 沙箱中的vue 编译流程
              • loader处理
                • @vue/compiler-sfc 编译
                  • babel 编译,处理css
                    • eval执行
                    • 最后
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档