iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >如何在JavaScript中使用宏
  • 682
分享到

如何在JavaScript中使用宏

2023-06-14 23:06:29 682人浏览 薄情痞子
摘要

今天就跟大家聊聊有关如何在javascript中使用宏,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。JavaScript是什么js是JavaScript的简称,它是一种直译式的脚本语

今天就跟大家聊聊有关如何在javascript中使用宏,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。

JavaScript是什么

js是JavaScript的简称,它是一种直译式的脚本语言,其解释器被称为JavaScript引擎,是浏览器的一部分,主要用于WEB开发,可以给网站添加各种各样的动态效果,让网页更加美观。

在语言当中,宏常见用途有实现 DSL 。通过宏,开发者可以自定义一些语言的格式,比如实现 JSX 语法。在 WASM 已经实现的今天,用其他语言来写网页其实并不是没有可能。像 Rust 语言就带有强大的宏功能,这使得基于 Rust 的 Yew 框架,不需要实现类似 Babel 的东西,而是靠语言本身就能实现类似 JSX 的语法。 一个 Yew 组件的例子,支持类 JSX 的语法。

impl Component for MyComponent {    // ...    fn view(&self) -> html {        let onclick = self.link.callback(|_| Msg::Click);        html! {            <button onclick=onclick>{ self.props.button_text }</button>        }    }}

JavaScript 宏的局限性

不同于 Rust ,JavaScript 本身是不支持宏的,所以整个工具链也是没有考虑宏的。因此,你是可以写个识别自定义语法的宏,但是由于配套的工具链并不支持,比如最常见的 vscodetypescript ,你会得到一个语法错误。同样对于 babel 本身所用的 parser 也是不支持扩展语法的,除非你另 Fork 出来一个 Babel 。因此 babel-plugin-Macros 不支持自定义语法。 不过,借助模板字符串函数,我们可以曲线救国,至少获得部分自定义语法树的能力。 一个 GraphQL 的例子,支持在 JavaScript 中直接编写 GraphQL。

import { gql } from 'graphql.macro';const query = gql`  query User {    user(id: 5) {      lastName      ...UserEntry1    }  }`;//  在编译期会转换成 ↓ ↓ ↓ ↓ ↓ ↓const query = {  "kind": "Document",  "definitions": [{    ...

为什么要用宏而非 Babel 插件

Babel 插件的能力确实远大于宏,而且有些情况下确实是不得不用插件。宏比起 Babel 插件好的一点在于,宏的理念在于开箱即用。使用 React 的开发者,相信都听过的大名鼎鼎的 Create-React-App ,帮你封装好了各种底层细节,开发者专注于编写代码即可。但是 CRA 的问题在于其封装的太严了,但凡你有一点需要自定义 Babel 插件的需求,基本上就需要执行yarn react-script eject,将所有底层细节暴露出来。 而对于宏来说,你只需要在项目的 Babel 配置内添加一个 babel-plugin-macros 插件,那么对于任何自定义的 Babel 宏都可以完美支持,而不是像插件一样,需要下载各种各样的插件。 CRA 已经内置了 babel-plugin-macros ,你可以在 CRA 项目中使用任意的 Babel 宏。

如何写一个宏?

介绍

一个宏非常像一个 Babel 插件,因此事先了解如何编写 Babel 插件是非常有帮助的,对于如何编写 Babel 插件, Babel 官方有一本手册,专门介绍了如何从零编写一个 Babel 插件。 在知道如何编写 Babel 插件之后,我们首先通过一个使用宏的例子,来介绍下, Babel 是如何识别文件中的宏的。是某种的特殊的语法,还是用烂的 $ 符号?

import preval from 'preval.macro'const one = preval`module.exports = 1 + 2 - 1 - 1`

这是非常常见的一个宏,其作用是在编译期间执行字符串中的 JavaScript 代码,然后将执行的结果替换到相应的地方,如上的代码在编译期会被展开为:

import preval from 'preval.macro'const one = 1

从使用来方式来看,唯一与识别宏沾点关系的就是*.macro字符,这也确实就是 Babel 如何识别宏的方式,实际上不仅对于*.macro的形式, Babel 认为库名匹配正则/[./]macro(\.c?js)?$/表达式的库就是 Babel 宏,这些匹配表达式的一些例子:

'my.macro''my.macro.js''my.macro.cjs''my/macro''my/macro.js''my/macro.cjs'

编写

接下来,我们将简单编写一个importURL宏,其作用是通过 url 来引入一些库,并在编译期间将这些库的代码预先拉取下来,处理一下然后引入到文件中。我知道有些 webpack 插件已经支持 从 url 来引入库,不过这同样是一个很好的例子来学习如何编写宏,为了有趣!以及如何在 nodejs 中发起同步请求! :)

准备

首先创建一个名为 importURL 的文件夹,执行npm init -y,来快速创建一个项目。在项目使用宏的人需要安装babel-plugin-macros,同样的,编写宏的同样需要安装这个插件,在写之前,我们也需要提前安装一些其他的库来辅助我们编写宏,在开发之前,需要事先:

  • 在package.JSON将name改为import-url.macro,符合 Babel 识别宏的格式

  • 我们需要用 Babel 提供的辅助方法来创建宏。执行yarn add babel-plugin-macros

  • yarn add fs-extra,一个更容易使用的代替 nodefs模块的库

  • yarn add find-root,编写宏的过程我们需要根据所处理文件的路径找到其所在的工作目录,从而写入缓存,这是一个已经封装好的库

示例

我们的目标就是将如下代码转换成

import importURL from 'importurl.macros';const React = importURL('https://unpkg.com/react@17.0.1/umd/react.development.js');// 编译成import importURL from 'importurl.macros';const React = require('../cache/pkg1.js');

我们会解析代码 importURL 函数的第一个参数,当做远程库的地址,然后在编译期间同步的通过 Get 请求拉取代码内容。然后写入项目顶层文件夹下.chache下,并替换相应的 importURL 语句成require(...)语句,路径...则是使用importURL的文件相对.cache文件中的相对路径,使得 webpack 在最终打包的时候能够找到对应的代码。

开始

我们先看看最终的代码长什么样子

import { execSync } from 'child_process';import findRoot from 'find-root';import path from 'path';import fse from 'fs-extra';import { createMacro } from 'babel-plugin-macros';const syncGet = (url) => {  const data = execSync(`curl -L ${url}`).toString();  if (data === '') {    throw new Error('empty data');  }  return data;}let count = 0;export const genUniqueName = () => `pkg${++count}.js`;module.exports = createMacro((ctx) => {  const {    references, // 文件中所有对宏的引用    babel: {      types: t,    }  } = ctx;  // babel 会把当前处理的文件路径设置到 ctx.state.filename  const workspacePath = findRoot(ctx.state.filename);  // 计算出缓存文件夹  const cacheDirPath = path.join(workspacePath, '.cache');  //  const calls = references.default.map(path => path.findParent(path => path.node.type === 'CallExpression' ));  calls.forEach(nodePath => {    // 确定 astNode 的类型    if (nodePath.node.type === 'CallExpression') {      // 确定函数的第一个参数是纯字符串      if (nodePath.node.arguments[0]?.type === 'StringLiteral') {        // 获取一个参数,当做远程库的地址        const url = nodePath.node.arguments[0].value;        // 根据 url 拉取代码        const codes = syncGet(url);        // 生成一个唯一包名,防止冲突        const pkgName = genUniqueName();        // 确定最终要写入的文件路径        const cahceFilename = path.join(cacheDirPath, pkgName);        // 通过 fse 库,将内容写入, outputFileSync 会自动创建不存在的文件夹        fse.outputFileSync(cahceFilename, codes);        // 计算出相对路径        const relativeFilename = path.relative(ctx.state.filename, cahceFilename);        // 最终计算替换 importURL 语句        nodePath.replaceWith(t.stringLiteral(`require('${relativeFilename}')`))      }    }  });});

创建一个宏

我们通过createMacro函数来创建一个宏,createMacro接受我们编写的函数当做参数来生成一个宏,但实际上我们并不关心createMacro的返回时值是什么,因为我们的代码最终都将会被自己替换掉,不会在运行期间执行到。 我们编写的函数的第一个参数是 Babel 传递给我们的一些状态,我们可以大概看下其类型都有什么。

function createMacro(handler: MacroHandler, options?: Options): any;interface MacroParams {      references: { default: Babel.NodePath[] } & References;      state: Babel.PluginPass;      babel: typeof Babel;      config?: { [key: string]: any };  }export interface PluginPass {    file: BabelFile;    key: string;    opts: PluginOptions;    cwd: string;    filename: string;    [key: string]: unknown;}

可视化 AST

我们可以通过astexplorer来观察我们将要处理代码的语法树,对于如下代码

import importURL from 'importurl.macros';const React = importURL('Https://unpkg.com/react@17.0.1/umd/react.development.js');

会生成如下语法树

如何在JavaScript中使用宏

红色标红的语法树节点,就是 Babel 会通过ctx.references传递给我们的,因此我们需要通过.findParent()方法来向上找到父节点CallExpresstion,才能去获取arguments属性下的参数,拿到远程库的 URL 地址。

同步请求

这里的一个难点在于, Babel 不支持异步转换,所有的转换操作都是同步的,因此在发起请求时也必须是同步的请求。我本来以为这是一件很简单的事情, Node 会提供一个类似sync: true的选项。但是并没有的, Node 确实不支持任何同步请求,除非你选择用下面这种很怪异的方式

const syncGet = (url) => {  const data = execSync(`curl -L ${url}`).toString();  if (data === '') {    throw new Error('empty data');  }  return data;}

看完上述内容,你们对如何在JavaScript中使用宏有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注编程网精选频道,感谢大家的支持。

--结束END--

本文标题: 如何在JavaScript中使用宏

本文链接: https://www.lsjlt.com/news/275768.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 如何在JavaScript中使用宏
    今天就跟大家聊聊有关如何在JavaScript中使用宏,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。JavaScript是什么JS是JavaScript的简称,它是一种直译式的脚本语...
    99+
    2023-06-14
  • 在JavaScript中如何使用宏详解
    在语言当中,宏常见用途有实现 DSL 。通过宏,开发者可以自定义一些语言的格式,比如实现 JSX 语法。在 WASM 已经实现的今天,用其他语言来写网页其实并不是没有可能。像 Rus...
    99+
    2024-04-02
  • 如何在 JavaScript 中使用 Go?
    随着 Web 应用程序的复杂性不断增加,Web 开发人员不断寻找更好的方式来构建更高效的应用程序。其中一种最新的趋势是将服务器端代码从传统的语言(如 PHP、Ruby 或 Python)转移到更快、更安全、更强大的语言中。 其中一种语言是...
    99+
    2023-08-22
    npm 函数 javascript
  • 如何在JavaScript中使用undefined
    这篇文章给大家介绍如何在JavaScript中使用undefined,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。javascript是一种什么语言javascript是一种动态类型、弱类型的语言,基于对象和事件驱动并...
    99+
    2023-06-14
  • excel表格宏如何使用
    使用Excel的宏功能可以自动化执行一系列操作,从而提高工作效率。下面是使用Excel表格宏的步骤:1. 打开Excel表格。2. ...
    99+
    2023-10-09
    excel
  • 如何在JavaScript中使用缓存?
    当我们在开发JavaScript应用程序时,我们经常需要处理大量数据和复杂的计算。在这种情况下,我们需要考虑性能问题,以确保应用程序在运行时能够快速响应并保持流畅。在这方面,缓存是一个非常重要的工具,它可以帮助我们提高应用程序的性能并减少网...
    99+
    2023-11-13
    数据类型 缓存 javascript
  • C++中如何使用内置的调试宏
    这篇文章主要为大家展示了“C++中如何使用内置的调试宏”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“C++中如何使用内置的调试宏”这篇文章吧。使用内置的调试宏在...
    99+
    2024-04-02
  • wps宏编辑器如何使用
    这篇文章主要讲解了“wps宏编辑器如何使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“wps宏编辑器如何使用”吧!首先打开wps,进入表格,点击上方“开发工具”打开后,点击下面的“js宏”...
    99+
    2023-07-02
  • 如何在 Python 中使用 JavaScript 代码?
    在现代 Web 开发中,前端技术的重要性越来越受到重视。JavaScript 是前端开发中不可或缺的一部分,它可以让网站具备更加丰富的交互体验和更高的动态性。Python 是一种广泛应用于 Web 开发、数据科学和人工智能等领域的编程语言。...
    99+
    2023-10-01
    文件 numpy javascript
  • item方法如何在javascript中使用
    这篇文章给大家介绍item方法如何在javascript中使用,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。创建一个名称为 item   的html文件添加一个button按钮,在点击事件中加入自定义函数 &...
    99+
    2023-06-14
  • 如何在JavaScript数组中使用reduce
    如何在JavaScript数组中使用reduce?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。JavaScript的特点1.JavaScript主要用来向HTM...
    99+
    2023-06-14
  • 如何在 Java 和 JavaScript 中使用 Numpy?
    如何在 Java 和 JavaScript 中使用 Numpy? Numpy是Python中最流行的科学计算库之一,它提供了高效的数学和科学计算功能,包括数组操作、线性代数、傅里叶变换、随机数生成和统计分析等等。但是,Numpy并不只是Py...
    99+
    2023-06-28
    文件 javascript numpy
  • sqrt方法如何在javascript中使用
    这篇文章给大家介绍sqrt方法如何在javascript中使用,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。javascript sqrt方法用于返回一个数的平方根,其使用语法是“Math.sqrt(x)”,其参数x表...
    99+
    2023-06-14
  • 如何在javascript中使用then方法
    如何在javascript中使用then方法?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。javascript then方法是异步执行,就是当【.then()】前的方法执行完后...
    99+
    2023-06-14
  • 如何在javascript中使用exec方法
    本篇文章为大家展示了如何在javascript中使用exec方法,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。javascript的exec方法用于检索字符串中的正则表达式的匹配,其使用语法是“Re...
    99+
    2023-06-14
  • 如何在javascript中使用 push()方法
    这篇文章给大家介绍如何在javascript中使用 push()方法,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。JavaScript的特点1.JavaScript主要用来向HTML页面添加交互行为。2.JavaScr...
    99+
    2023-06-14
  • 如何在JavaScript中使用bind()函数
    这篇文章给大家介绍如何在JavaScript中使用bind()函数,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。1.语法:function.bind(thisArg[,arg1[,arg2[, ...]]])...
    99+
    2023-06-14
  • 如何在javascript中使用call方法
    这期内容当中小编将会给大家带来有关如何在javascript中使用call方法,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。js中call()方法的用法1.call方法语法:call(thisobj,[a...
    99+
    2023-06-14
  • 如何在JavaScript中使用join函数
    如何在JavaScript中使用join函数?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。Java的优点是什么1. 简单,只需理解基本的概念,就可以编写适合于各...
    99+
    2023-06-14
  • 如何在JavaScript中使用Java数组?
    当我们在进行JavaScript开发时,有时候需要使用Java数组,这时我们该怎么办呢?在本文中,我们将会介绍如何在JavaScript中使用Java数组。 Java数组是Java中最基本的数据结构之一,它可以存储一组相同类型的数据。在Ja...
    99+
    2023-10-25
    数组 javascript 函数
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作