├── examples
└── vite
│ ├── .gitignore
│ ├── package.json
│ ├── style.css
│ ├── public
│ └── vite.svg
│ ├── index.js
│ ├── utils.js
│ ├── index.html
│ └── package-lock.json
├── tests
├── unit
│ ├── samples
│ │ ├── cjs_require.js
│ │ ├── esm_top_level_import.js
│ │ ├── esm_default_export.js
│ │ ├── esm_named_export.js
│ │ ├── unknown.js
│ │ ├── esm_import_meta.js
│ │ ├── mixed.js
│ │ ├── cjs_require_in_string.js
│ │ ├── esm_create_require.js
│ │ ├── cjs_create_require_scope.js
│ │ ├── cjs_require_check.js
│ │ ├── esm_entice_cjs.js
│ │ ├── cjs_entice_esm.js
│ │ └── iter_regex_ambiguity.js
│ └── main.rs
└── integration
│ └── main.rs
├── .gitignore
├── dist-proxy
├── index.d.ts
└── index.js
├── Cargo.toml
├── .github
└── workflows
│ ├── ci.yml
│ └── deploy.yml
├── LICENSE
├── package.json
├── README.md
└── src
├── lib.rs
├── walk.rs
└── utils.rs
/examples/vite/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 |
--------------------------------------------------------------------------------
/tests/unit/samples/cjs_require.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 |
--------------------------------------------------------------------------------
/tests/unit/samples/esm_top_level_import.js:
--------------------------------------------------------------------------------
1 | import foo from 'bar'
--------------------------------------------------------------------------------
/tests/unit/samples/esm_default_export.js:
--------------------------------------------------------------------------------
1 | export default 'foo'
2 |
--------------------------------------------------------------------------------
/tests/unit/samples/esm_named_export.js:
--------------------------------------------------------------------------------
1 | export const foo = 'bar'
2 |
--------------------------------------------------------------------------------
/tests/unit/samples/unknown.js:
--------------------------------------------------------------------------------
1 | console.log('require("blabla")')
2 |
--------------------------------------------------------------------------------
/tests/unit/samples/esm_import_meta.js:
--------------------------------------------------------------------------------
1 | console.log(import.meta.url)
2 |
--------------------------------------------------------------------------------
/tests/unit/samples/mixed.js:
--------------------------------------------------------------------------------
1 | import foo from 'bar'
2 | module.exports = foo
3 |
--------------------------------------------------------------------------------
/tests/unit/samples/cjs_require_in_string.js:
--------------------------------------------------------------------------------
1 | const text = `hello ${typeof require('fs')}`
2 |
--------------------------------------------------------------------------------
/tests/unit/samples/esm_create_require.js:
--------------------------------------------------------------------------------
1 | import { createRequire } from 'module'
2 |
3 | const require = createRequire(import.meta.url)
4 |
5 | require('foo')
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /tests/integration/samples
3 | Cargo.lock
4 |
5 | # examples
6 | pnpm-lock.yaml
7 | package-lock.json
8 | yarn.lock
9 |
10 | # this example is deployed
11 | !/examples/vite/package-lock.json
12 |
--------------------------------------------------------------------------------
/dist-proxy/index.d.ts:
--------------------------------------------------------------------------------
1 | export type JsSyntax = 'ESM' | 'CJS' | 'Mixed' | 'Unknown'
2 |
3 | export function guessJsSyntax(s: string): JsSyntax
4 |
5 | export {
6 | default as init,
7 | initSync,
8 | InitInput,
9 | InitOutput
10 | } from '../dist/index.js'
11 |
--------------------------------------------------------------------------------
/tests/unit/samples/cjs_create_require_scope.js:
--------------------------------------------------------------------------------
1 | const { createRequire } = require('module')
2 |
3 | function main() {
4 | const require = createRequire(__filename)
5 | require('foo')
6 | }
7 |
8 | // make sure main's `require` affects it's scope only
9 | require('foo')
10 | main()
11 |
--------------------------------------------------------------------------------
/tests/unit/samples/cjs_require_check.js:
--------------------------------------------------------------------------------
1 | let foo
2 |
3 | // NOTE: cases like this ideally means that this file is not CJS, as it works in ESM too.
4 | // but it is really really hard to detect unless we do control flow analysis.
5 | if (typeof require !== 'undefined') {
6 | foo = require('foo')
7 | } else {
8 | foo = 'bar'
9 | }
10 |
--------------------------------------------------------------------------------
/examples/vite/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "fmu": "file:../.."
13 | },
14 | "devDependencies": {
15 | "vite": "^3.0.2"
16 | }
17 | }
--------------------------------------------------------------------------------
/dist-proxy/index.js:
--------------------------------------------------------------------------------
1 | import init, {
2 | initSync,
3 | guessJsSyntax as _guessJsSyntax
4 | } from '../dist/index.js'
5 |
6 | export function guessJsSyntax(s) {
7 | const result = _guessJsSyntax(s)
8 | switch (result) {
9 | case 0:
10 | return 'ESM'
11 | case 1:
12 | return 'CJS'
13 | case 2:
14 | return 'Mixed'
15 | case 3:
16 | return 'Unknown'
17 | }
18 | }
19 |
20 | export { init, initSync }
21 |
--------------------------------------------------------------------------------
/tests/unit/samples/esm_entice_cjs.js:
--------------------------------------------------------------------------------
1 | // In this file, we make it explicitly ESM, but entice it to be CJS.
2 | // Make sure the parser knows this is still ESM.
3 |
4 | import foo from 'bar'
5 | export const bar = 'foo'
6 | export { foo }
7 |
8 | // require('hello')
9 |
10 | /*
11 | require('world')
12 | */
13 |
14 | export function entice(module, exports) {
15 | let a = `require('bar')`
16 | module.exports = { strange: 'package' }
17 | exports = 'chaos!'
18 | const regexMadness = /require('asdsd')/gi
19 | }
20 |
--------------------------------------------------------------------------------
/tests/unit/samples/cjs_entice_esm.js:
--------------------------------------------------------------------------------
1 | // In this file, we make it explicitly CJS, but entice it to be ESM.
2 | // Make sure the parser knows this is still CJS.
3 |
4 | // import foo from 'bar'
5 | // export const bar = 'foo'
6 | // export { foo }
7 |
8 | import('arrgh')
9 |
10 | // import('hello')
11 |
12 | /*
13 | require('world')
14 | */
15 |
16 | require('fs')
17 |
18 | module.exports = function entice() {
19 | let a = `import foo from 'bar'`
20 | }
21 |
22 | exports = 'wowzah'
23 |
24 | const regexMadness = /import foo from 'bar'/gi
25 |
--------------------------------------------------------------------------------
/tests/unit/samples/iter_regex_ambiguity.js:
--------------------------------------------------------------------------------
1 | // Regex is surprisingly annoying to parse because of division.
2 | // Credit: https://github.com/guybedford/es-module-lexer/blob/559a550318fcdfe20c60cb322c147905b5aadf9f/test/_unit.cjs#L510
3 | /as)df/; x();
4 | a / 2; ' / '
5 | while (true)
6 | /test'/
7 | x-/a'/g
8 | try {}
9 | finally{}/a'/g
10 | (x);{f()}/d'export { b }/g
11 | ;{}/e'/g;
12 | {}/f'/g
13 | a / 'b' / c;
14 | /a'/ - /b'/;
15 | +{} /g -'/g'
16 | ('a')/h -'/g'
17 | if //x
18 | ('a')/i'/g;
19 | /asdf/ / /as'df/; // '
20 | p = `\${/test/ + 5}`;
21 | /regex/ / x;
22 | function m() {
23 | return /*asdf8*// 5/;
24 | }
25 | export { a };
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "fmu"
3 | version = "0.0.1"
4 | authors = ["Bjorn Lu"]
5 | edition = "2021"
6 | description = "Fast module utilities"
7 | repository = "https://github.com/bluwy/fmu"
8 | license = "MIT"
9 | keywords = ["javascript", "module"]
10 | categories = ["web-programming"]
11 | include = ["/src"]
12 | publish = false
13 |
14 | [lib]
15 | crate-type = ["cdylib", "rlib"]
16 |
17 | [profile.release]
18 | lto = true
19 | opt-level = "s"
20 |
21 | [dependencies]
22 | wasm-bindgen= "0.2.81"
23 | console_error_panic_hook = { version = "0.1.7", optional = true }
24 |
25 | [dev-dependencies]
26 | hyper = { version = "0.14", features = ["full"] }
27 | hyper-tls = "0.5.0"
28 | tokio = { version = "1", features = ["full"] }
29 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | paths:
8 | - 'lib/**'
9 | - 'tests/**'
10 | - '.github/workflows/ci.yml'
11 | pull_request:
12 | paths:
13 | - 'lib/**'
14 | - 'tests/**'
15 | - '.github/workflows/ci.yml'
16 |
17 | concurrency:
18 | group: ${{ github.workflow }}-${{ github.event.number || github.sha }}
19 | cancel-in-progress: true
20 |
21 | jobs:
22 | test:
23 | name: Test
24 | runs-on: ubuntu-latest
25 | timeout-minutes: 5
26 | steps:
27 | - name: Checkout repo
28 | uses: actions/checkout@v3
29 | - name: Setup Rust cache
30 | uses: Swatinem/rust-cache@v2
31 | - name: Run tests
32 | run: cargo test
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Bjorn Lu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fmu",
3 | "version": "0.0.1",
4 | "description": "Fast module utilities",
5 | "author": "Bjorn Lu",
6 | "license": "MIT",
7 | "type": "module",
8 | "sideEffects": false,
9 | "types": "./dist/index.d.ts",
10 | "exports": {
11 | ".": {
12 | "types": "./dist-proxy/index.d.ts",
13 | "import": "./dist-proxy/index.js"
14 | },
15 | "./wasm": {
16 | "default": "./dist/index_bg.wasm"
17 | }
18 | },
19 | "scripts": {
20 | "dev": "npm run build -- -- --features console_error_panic_hook",
21 | "build": "wasm-pack build --target web --out-dir dist --out-name index",
22 | "test": "cargo test"
23 | },
24 | "files": [
25 | "dist/index_bg.wasm",
26 | "dist/index.js",
27 | "dist/index.d.ts",
28 | "dist-proxy/index.js",
29 | "dist-proxy/index.d.ts"
30 | ],
31 | "funding": "https://bjornlu.com/sponsor",
32 | "repository": {
33 | "type": "git",
34 | "url": "https://github.com/bluwy/fmu"
35 | },
36 | "bugs": {
37 | "url": "https://github.com/bluwy/fmu/issues"
38 | },
39 | "keywords": [
40 | "javascript",
41 | "module"
42 | ]
43 | }
--------------------------------------------------------------------------------
/examples/vite/style.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
3 | font-size: 16px;
4 | line-height: 24px;
5 | font-weight: 400;
6 |
7 | color-scheme: light dark;
8 | color: rgba(255, 255, 255, 0.87);
9 | background-color: #242424;
10 |
11 | font-synthesis: none;
12 | text-rendering: optimizeLegibility;
13 | -webkit-font-smoothing: antialiased;
14 | -moz-osx-font-smoothing: grayscale;
15 | -webkit-text-size-adjust: 100%;
16 | }
17 |
18 | a {
19 | font-weight: 500;
20 | color: #646cff;
21 | text-decoration: inherit;
22 | }
23 | a:hover {
24 | color: #535bf2;
25 | }
26 |
27 | body {
28 | margin: 0;
29 | display: flex;
30 | place-items: center;
31 | min-width: 320px;
32 | min-height: 100vh;
33 | }
34 |
35 | h1 {
36 | font-size: 3.2em;
37 | line-height: 1.1;
38 | }
39 |
40 | #app {
41 | max-width: 1280px;
42 | margin: 0 auto;
43 | padding: 2rem;
44 | text-align: center;
45 | }
46 |
47 | #js-textarea {
48 | width: 30rem;
49 | height: 20rem;
50 | max-width: 90%;
51 | padding: 0.5rem;
52 | font-size: 1em;
53 | font-family: 'Courier New', Courier, monospace;
54 | }
55 |
56 | #metadata {
57 | opacity: 0.5;
58 | font-size: 0.9em;
59 | }
60 |
61 | @media (prefers-color-scheme: light) {
62 | :root {
63 | color: #213547;
64 | background-color: #ffffff;
65 | }
66 | a:hover {
67 | color: #747bff;
68 | }
69 | button {
70 | background-color: #f9f9f9;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/examples/vite/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/vite/index.js:
--------------------------------------------------------------------------------
1 | import { init, guessJsSyntax } from 'fmu'
2 | import wasmUrl from 'fmu/wasm?url'
3 | // import { getCodeFormat } from './utils'
4 | import './style.css'
5 |
6 | const textarea = document.getElementById('js-textarea')
7 | const result = document.getElementById('result')
8 | const timing = document.getElementById('timing')
9 | const metadata = document.getElementById('metadata')
10 |
11 | init(wasmUrl).then(() => {
12 | update()
13 | // debounce update
14 | let t
15 | textarea.addEventListener('input', () => {
16 | clearTimeout(t)
17 | t = setTimeout(update, 300)
18 | })
19 | })
20 |
21 | function update() {
22 | try {
23 | const start = performance.now()
24 | result.innerText = guessJsSyntax(textarea.value)
25 | const end = performance.now()
26 | timing.innerText = ` (${(end - start).toPrecision(3)}ms)`
27 |
28 | // So turns out that JS regex is faster than Rust wasm but the Rust implementation
29 | // is currently more correct. If we want to match the correctness in JS, Rust should win.
30 | // For now let's hide it :P
31 | //
32 | // const start2 = performance.now()
33 | // const format = getCodeFormat(textarea.value)
34 | // const end2 = performance.now()
35 | // metadata.innerText = `Regex is ${format} (${(end2 - start2).toPrecision(
36 | // 3
37 | // )}ms)`
38 | } catch (e) {
39 | result.innerText = '[Error] see console for details'
40 | timing.innerText = ''
41 | metadata.innerText = ''
42 | throw e
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | paths:
8 | - 'lib/**'
9 | - 'dist-proxy/**'
10 | - 'examples/vite/**'
11 | - '.github/workflows/deploy.yml'
12 |
13 | concurrency:
14 | group: ${{ github.workflow }}-${{ github.sha }}
15 | cancel-in-progress: true
16 |
17 | jobs:
18 | deploy:
19 | # prevents this action from running on forks
20 | if: github.repository == 'bluwy/fmu'
21 | name: Deploy
22 | runs-on: ubuntu-latest
23 | timeout-minutes: 5
24 | steps:
25 | - name: Checkout repo
26 | uses: actions/checkout@v3
27 | - name: Setup Rust cache
28 | uses: Swatinem/rust-cache@v2
29 | - name: Install wasm-pack
30 | run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
31 | - name: Build wasm
32 | run: npm run build
33 | - name: Install dependencies
34 | run: npm install
35 | working-directory: examples/vite
36 | - name: Build site
37 | run: npm run build -- --base /fmu/
38 | working-directory: examples/vite
39 | - name: Setup git user
40 | run: |
41 | git config --global user.name "github-actions[bot]"
42 | git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
43 | - name: Deploy site
44 | run: |
45 | git init
46 | git checkout -b site
47 | git add -A
48 | git commit -m 'deploy'
49 | git remote add origin https://bluwy:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
50 | git push -f origin site:gh-pages
51 | working-directory: examples/vite/dist
52 |
--------------------------------------------------------------------------------
/tests/unit/main.rs:
--------------------------------------------------------------------------------
1 | use fmu::{guess_js_syntax, JsSyntax};
2 | use std::fs;
3 |
4 | #[test]
5 | fn esm() {
6 | assert_eq!(guess_js_syntax(&rs("esm_default_export")), JsSyntax::ESM);
7 | assert_eq!(guess_js_syntax(&rs("esm_named_export")), JsSyntax::ESM);
8 | assert_eq!(guess_js_syntax(&rs("esm_top_level_import")), JsSyntax::ESM);
9 | assert_eq!(guess_js_syntax(&rs("esm_import_meta")), JsSyntax::ESM);
10 | assert_eq!(guess_js_syntax(&rs("esm_create_require")), JsSyntax::ESM);
11 | assert_eq!(guess_js_syntax(&rs("esm_entice_cjs")), JsSyntax::ESM);
12 | }
13 |
14 | #[test]
15 | fn cjs() {
16 | assert_eq!(guess_js_syntax(&rs("cjs_require")), JsSyntax::CJS);
17 | assert_eq!(guess_js_syntax(&rs("cjs_require_in_string")), JsSyntax::CJS);
18 | assert_eq!(
19 | guess_js_syntax(&rs("cjs_create_require_scope")),
20 | JsSyntax::CJS
21 | );
22 | assert_eq!(guess_js_syntax(&rs("cjs_require_check")), JsSyntax::CJS);
23 | assert_eq!(guess_js_syntax(&rs("cjs_entice_esm")), JsSyntax::CJS);
24 | }
25 |
26 | #[test]
27 | fn mixed() {
28 | assert_eq!(guess_js_syntax(&rs("mixed")), JsSyntax::Mixed);
29 | }
30 |
31 | #[test]
32 | fn unknown() {
33 | assert_eq!(guess_js_syntax(&rs("unknown")), JsSyntax::Unknown);
34 | }
35 |
36 | #[test]
37 | fn iter() {
38 | assert_eq!(guess_js_syntax(&rs("iter_regex_ambiguity")), JsSyntax::ESM);
39 | }
40 |
41 | #[test]
42 | fn quick() {
43 | assert_eq!(guess_js_syntax(" n require "), JsSyntax::CJS);
44 | }
45 |
46 | // read sample. shorten so assertions are all single-line.
47 | fn rs(name: &str) -> String {
48 | let s = match fs::read_to_string(format!("tests/unit/samples/{}.js", name)) {
49 | Err(err) => panic!("Couldn't open file: {}", err),
50 | Ok(value) => value,
51 | };
52 | s
53 | }
54 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | A collection of JS module utilities written in Rust
12 |
13 |
14 | ## Usage
15 |
16 | The package is published to npm as a WebAssembly module. Install with:
17 |
18 | ```bash
19 | $ npm install fmu
20 | ```
21 |
22 | ```js
23 | import { init, guessJsSyntax } from 'fmu'
24 |
25 | // initialize wasm (MUST call this before any other APIs)
26 | await init()
27 |
28 | const code = `exports.foo = 'bar'`
29 | console.log(await guessJsSyntax(code)) // "CJS"
30 | ```
31 |
32 | > **Note**: For Vite, you have to pass a custom URL to `init()`. For example:
33 | >
34 | > ```js
35 | > import { init } from 'fmu'
36 | > import wasmUrl from 'fmu/wasm?url'
37 | > await init(wasmUrl)
38 | > ```
39 |
40 | See [examples](./examples/) for usage with different setups.
41 |
42 | ## Development
43 |
44 | Follow the [official guide](https://www.rust-lang.org/tools/install) to install Rust. Also install [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/) to build Rust files into WebAssembly.
45 |
46 | ```bash
47 | # Build wasm for dev (e.g. testing examples)
48 | $ npm run dev
49 |
50 | # Build wasm for publishing
51 | $ npm run build
52 |
53 | # Run unit and integration tests
54 | $ cargo test
55 | ```
56 |
57 | ## Sponsors
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | ## License
66 |
67 | MIT
68 |
--------------------------------------------------------------------------------
/examples/vite/utils.js:
--------------------------------------------------------------------------------
1 | // Copy from publint to compare perf
2 |
3 | // Reference: https://github.com/unjs/mlly/blob/c5ae321725cbabe230c16c315d474c36eee6a30c/src/syntax.ts#L7
4 | const ESM_CONTENT_RE =
5 | /([\s;]|^)(import[\w,{}\s*]*from|import\s*['"*{]|export\b\s*(?:[*{]|default|type|function|const|var|let|async function)|import\.meta\b)/m
6 | export function isCodeEsm(code) {
7 | return ESM_CONTENT_RE.test(code)
8 | }
9 |
10 | // Reference: https://github.com/unjs/mlly/blob/c5ae321725cbabe230c16c315d474c36eee6a30c/src/syntax.ts#L15
11 | const CJS_CONTENT_RE =
12 | /([\s;]|^)(module.exports\b|exports\.\w|require\s*\(|global\.\w|Object\.(defineProperty|defineProperties|assign)\s*\(\s*exports\b)/m
13 | export function isCodeCjs(code) {
14 | return CJS_CONTENT_RE.test(code)
15 | }
16 |
17 | const MULTILINE_COMMENTS_RE = /\/\*(.|[\r\n])*?\*\//gm
18 | const SINGLELINE_COMMENTS_RE = /\/\/.*/g
19 | export function stripComments(code) {
20 | return code
21 | .replace(MULTILINE_COMMENTS_RE, '')
22 | .replace(SINGLELINE_COMMENTS_RE, '')
23 | }
24 |
25 | /**
26 | * @param {string} code
27 | * @returns {CodeFormat}
28 | */
29 | export function getCodeFormat(code) {
30 | code = stripComments(code)
31 | const isEsm = isCodeEsm(code)
32 | const isCjs = isCodeCjs(code)
33 | // In reality, a file can't have mixed ESM and CJS. It's syntactically incompatible in both environments.
34 | // But since we use regex, we can't correct identify ESM and CJS, so when this happens we should bail instead.
35 | // TODO: Yak shave a correct implementation.
36 | if (isEsm && isCjs) {
37 | return 'Mixed'
38 | } else if (isEsm) {
39 | return 'ESM'
40 | } else if (isCjs) {
41 | return 'CJS'
42 | } else {
43 | // If we can't determine the format, it's likely that it doesn't import/export and require/exports.
44 | // Meaning it's a side-effectful file, which would always match the `format`
45 | return 'Unknown'
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/examples/vite/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
fmu
12 |
13 |
Syntax is ...
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/tests/integration/main.rs:
--------------------------------------------------------------------------------
1 | use fmu::{guess_js_syntax, JsSyntax};
2 | use hyper::{body::to_bytes, Body, Client, Uri};
3 | use hyper_tls::HttpsConnector;
4 | use std::{fs, str::FromStr};
5 |
6 | #[tokio::test]
7 | async fn esm_svelte() -> Result<(), Box> {
8 | let res = fetch_unpkg(
9 | "esm_svelte",
10 | "https://unpkg.com/svelte@3.49.0/internal/index.mjs",
11 | )
12 | .await?;
13 | assert_eq!(guess_js_syntax(&res), JsSyntax::ESM);
14 | Ok(())
15 | }
16 |
17 | #[tokio::test]
18 | async fn esm_vite() -> Result<(), Box> {
19 | let res = fetch_unpkg(
20 | "esm_vite",
21 | "https://unpkg.com/vite@3.0.0/dist/node/chunks/dep-07a79996.js",
22 | )
23 | .await?;
24 | assert_eq!(guess_js_syntax(&res), JsSyntax::ESM);
25 | Ok(())
26 | }
27 |
28 | #[tokio::test]
29 | async fn esm_vue() -> Result<(), Box> {
30 | let res = fetch_unpkg(
31 | "esm_vue",
32 | "https://unpkg.com/vue@3.2.37/dist/vue.esm-browser.prod.js",
33 | )
34 | .await?;
35 | assert_eq!(guess_js_syntax(&res), JsSyntax::ESM);
36 | Ok(())
37 | }
38 |
39 | #[tokio::test]
40 | async fn cjs_svelte() -> Result<(), Box> {
41 | let res = fetch_unpkg(
42 | "cjs_svelte",
43 | "https://unpkg.com/svelte@3.49.0/internal/index.js",
44 | )
45 | .await?;
46 | assert_eq!(guess_js_syntax(&res), JsSyntax::CJS);
47 | Ok(())
48 | }
49 |
50 | #[tokio::test]
51 | async fn cjs_vue() -> Result<(), Box> {
52 | let res = fetch_unpkg(
53 | "cjs_vue",
54 | "https://unpkg.com/vue@3.2.37/dist/vue.cjs.prod.js",
55 | )
56 | .await?;
57 | assert_eq!(guess_js_syntax(&res), JsSyntax::CJS);
58 | Ok(())
59 | }
60 |
61 | #[tokio::test]
62 | async fn mixed_vite_plugin_svelte() -> Result<(), Box> {
63 | let res = fetch_unpkg(
64 | "mixed_vite_plugin_svelte",
65 | "https://unpkg.com/@sveltejs/vite-plugin-svelte@1.0.1/dist/index.js",
66 | )
67 | .await?;
68 | assert_eq!(guess_js_syntax(&res), JsSyntax::Mixed);
69 | Ok(())
70 | }
71 |
72 | async fn fetch_unpkg(name: &str, url: &str) -> Result> {
73 | let cache_file_path = format!("tests/integration/samples/{}.js", name);
74 | let result = match fs::read_to_string(&cache_file_path) {
75 | Err(_) => {
76 | // if fail to read, assume no exist. fetch and save to cache
77 | // TODO: skip if have no permissions instead for some reason
78 | let https = HttpsConnector::new();
79 | let client = Client::builder().build::<_, Body>(https);
80 | let resp = client.get(Uri::from_str(&url)?).await?;
81 | let body_bytes = to_bytes(resp.into_body()).await?;
82 | let content = String::from_utf8(body_bytes.to_vec()).unwrap();
83 | fs::create_dir("tests/integration/samples").ok();
84 | match fs::write(&cache_file_path, &content) {
85 | Err(err) => panic!("Couldn't write to {}: {}", cache_file_path, err),
86 | Ok(_) => (),
87 | }
88 | content
89 | }
90 | Ok(value) => value,
91 | };
92 | Ok(result)
93 | }
94 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | // suppress wasm-bindgen auto-generated name warning
2 | #![allow(non_snake_case, non_upper_case_globals)]
3 |
4 | mod utils;
5 | mod walk;
6 |
7 | use utils::{
8 | is_export_identifier, is_exports_identifier, is_function_param_declaration,
9 | is_import_identifier, is_meta_identifier, is_module_identifier, is_require_identifier,
10 | is_var_declaration,
11 | };
12 | use walk::{walk, WalkCallbackResult};
13 | use wasm_bindgen::prelude::*;
14 |
15 | #[wasm_bindgen]
16 | #[derive(Debug, PartialEq, Eq)]
17 | pub enum JsSyntax {
18 | ESM,
19 | CJS,
20 | Mixed,
21 | Unknown,
22 | }
23 |
24 | // detect file syntax esm or cjs
25 | #[wasm_bindgen(js_name = "guessJsSyntax")]
26 | pub fn guess_js_syntax(s: &str) -> JsSyntax {
27 | #[cfg(feature = "console_error_panic_hook")]
28 | console_error_panic_hook::set_once();
29 |
30 | let mut is_esm = false;
31 | let mut is_cjs = false;
32 |
33 | // shadowing
34 | // default depth is 0, every open brace increments, closing brace decrements.
35 | // this happens for JS objects too but for us, it's good enough
36 | let mut scope_depth: usize = 0;
37 | let mut require_shadowed_depth = usize::MAX;
38 | let mut module_shadowed_depth = usize::MAX;
39 | let mut exports_shadowed_depth = usize::MAX;
40 |
41 | walk(s, |b, i, c| {
42 | print!("{}", c as char);
43 | if is_esm && is_cjs {
44 | return WalkCallbackResult::Break;
45 | }
46 |
47 | // esm specific detection
48 | if !is_esm {
49 | // top-level import
50 | if is_import_identifier(&b, *i) {
51 | // TODO: handle space between import.meta, but why would someone do that
52 | if b[*i + 1] == b'.' && is_meta_identifier(&b, *i + 2) {
53 | is_esm = true;
54 | *i += 11;
55 | } else {
56 | // TODO: handle \r\n?
57 | for &v in b[*i + 6..].iter() {
58 | if v == b'\'' || v == b'"' || v == b'{' || v == b'\n' {
59 | is_esm = true;
60 | break;
61 | } else if v == b'(' {
62 | // dynamic import
63 | break;
64 | }
65 | }
66 | *i += 6;
67 | }
68 | return WalkCallbackResult::Continue;
69 | }
70 |
71 | // top-level export
72 | if is_export_identifier(&b, *i) {
73 | is_esm = true;
74 | *i += 6;
75 | return WalkCallbackResult::Continue;
76 | }
77 | }
78 |
79 | if !is_cjs {
80 | // track scope depth
81 | // NOTE: track in cjs only as it's only relevant for it
82 | // TODO: track `=>` and `?:` scoped (pita)
83 | if c == b'{' {
84 | scope_depth += 1;
85 | } else if c == b'}' {
86 | scope_depth = scope_depth.saturating_sub(1);
87 | // re-concile shadowed depth, if we exit the scope that has been
88 | // shadowed by require, module, or exports, reset them
89 | if scope_depth < require_shadowed_depth {
90 | require_shadowed_depth = usize::MAX;
91 | }
92 | if scope_depth < module_shadowed_depth {
93 | module_shadowed_depth = usize::MAX;
94 | }
95 | if scope_depth < exports_shadowed_depth {
96 | exports_shadowed_depth = usize::MAX;
97 | }
98 | }
99 |
100 | // require reference
101 | if scope_depth < require_shadowed_depth && is_require_identifier(&b, *i) {
102 | if is_var_declaration(&b, *i) {
103 | require_shadowed_depth = scope_depth;
104 | } else if is_function_param_declaration(&b, *i, *i + 7) {
105 | require_shadowed_depth = scope_depth + 1;
106 | } else {
107 | is_cjs = true;
108 | }
109 | *i += 7;
110 | return WalkCallbackResult::Continue;
111 | }
112 |
113 | // module reference
114 | if scope_depth < module_shadowed_depth && is_module_identifier(&b, *i) {
115 | if is_var_declaration(&b, *i) {
116 | module_shadowed_depth = scope_depth;
117 | } else if is_function_param_declaration(&b, *i, *i + 6) {
118 | module_shadowed_depth = scope_depth + 1;
119 | } else {
120 | is_cjs = true;
121 | }
122 | *i += 6;
123 | return WalkCallbackResult::Continue;
124 | }
125 |
126 | // exports reference
127 | if scope_depth < exports_shadowed_depth && is_exports_identifier(&b, *i) {
128 | if is_var_declaration(&b, *i) {
129 | exports_shadowed_depth = scope_depth;
130 | } else if is_function_param_declaration(&b, *i, *i + 7) {
131 | exports_shadowed_depth = scope_depth + 1;
132 | } else {
133 | is_cjs = true;
134 | }
135 | *i += 7;
136 | return WalkCallbackResult::Continue;
137 | }
138 | }
139 |
140 | return WalkCallbackResult::Continue;
141 | });
142 |
143 | if is_esm && is_cjs {
144 | return JsSyntax::Mixed;
145 | } else if is_esm {
146 | return JsSyntax::ESM;
147 | } else if is_cjs {
148 | return JsSyntax::CJS;
149 | } else {
150 | return JsSyntax::Unknown;
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/walk.rs:
--------------------------------------------------------------------------------
1 | use crate::utils::{
2 | get_nearest_non_whitespace_index_left, is_backslash_escaped,
3 | is_slash_preceded_by_regex_possible_keyword,
4 | };
5 |
6 | #[derive(Debug, Clone, Copy, PartialEq, Eq)]
7 | pub enum WalkCallbackResult {
8 | Continue,
9 | Break,
10 | }
11 |
12 | pub fn walk(s: &str, mut cb: F)
13 | where
14 | F: FnMut(&[u8], &mut usize, u8) -> WalkCallbackResult,
15 | {
16 | let mut i = 0;
17 | let b = s.as_bytes();
18 |
19 | // parsing state variables
20 | //
21 | // template literals can contain js via ${}, when this happens, we increase the depth by 1
22 | // for each js within. When the depth is more than 0, we need to do special checks to
23 | // know when we reach the end of the js.
24 | //
25 | // const foo = `hello ${world}`
26 | // |----------||------||-----||
27 | // 0 1
28 | let mut template_literal_js_depth = 0;
29 |
30 | while i < b.len() {
31 | let c = b[i];
32 |
33 | // single line comment, ignore until \n
34 | if c == b'/' && b[i + 1] == b'/' {
35 | let new_line_pos = match b[i + 2..].iter().position(|&v| v == b'\n' || v == b'\r') {
36 | Some(pos) => {
37 | if pos > 0 && b[i + 2 + pos + 1] == b'\n' {
38 | pos + 1
39 | } else {
40 | pos
41 | }
42 | }
43 | None => break, // assume reach end of file
44 | };
45 | i += 2 + new_line_pos + 1;
46 | continue;
47 | }
48 |
49 | // multi line comment, ignore until */
50 | if c == b'/' && b[i + 1] == b'*' {
51 | let closing_pos = match b[i + 3..]
52 | .iter()
53 | .enumerate()
54 | .position(|(j, &v)| v == b'/' && b[i + 3 + j - 1] == b'*')
55 | {
56 | Some(pos) => pos,
57 | None => break, // assume reach end of file
58 | };
59 | i += 3 + closing_pos + 1;
60 | continue;
61 | }
62 |
63 | // single and double quotes, ignore until quote end
64 | if c == b'\'' || c == b'"' {
65 | let closing_pos = match b[i + 1..].iter().enumerate().position(|(j, &v)| {
66 | if v == c {
67 | return !is_backslash_escaped(b, i + 1 + j);
68 | } else {
69 | return false;
70 | }
71 | }) {
72 | Some(pos) => pos,
73 | None => break, // assume reach end of file
74 | };
75 | // println!("quotes {}", &s[i..i + 1 + closing_pos + 1]);
76 | i += 1 + closing_pos + 1;
77 | continue;
78 | }
79 |
80 | // template literal, skip until ` or ${
81 | // template literal, but is inner js code, also check for closing }
82 | if c == b'`' || (template_literal_js_depth > 0 && c == b'}') {
83 | let closing_pos = match b[i + 1..].iter().enumerate().position(|(j, &v)| {
84 | // capture ${
85 | if v == b'$' && b[i + 1 + j + 1] == b'{' {
86 | return !is_backslash_escaped(b, i + 1 + j);
87 | }
88 | // capture `
89 | if v == b'`' {
90 | return !is_backslash_escaped(b, i + 1 + j);
91 | }
92 | false
93 | }) {
94 | Some(pos) => pos,
95 | None => break, // assume reach end of file
96 | };
97 | if b[i + 1 + closing_pos] == b'$' {
98 | // only increment for `, since for ${ it's already incremented
99 | if c == b'`' {
100 | template_literal_js_depth += 1;
101 | }
102 | // println!("temlitopen {}", &s[i..i + 1 + closing_pos + 2]);
103 | i += 1 + closing_pos + 2;
104 | } else {
105 | // only decrement for }, since for ` it's already decremented
106 | if c == b'}' {
107 | template_literal_js_depth -= 1;
108 | }
109 | // println!("temlitclose {}", &s[i..i + 1 + closing_pos + 1]);
110 | i += 1 + closing_pos + 1;
111 | }
112 | continue;
113 | };
114 |
115 | // skip regex
116 | if c == b'/' {
117 | let left = get_nearest_non_whitespace_index_left(&b, i);
118 | // knowing when a / is a division or the start of a regex ia PAIN. luckily this below
119 | // works good enough, by checking the we're not preceding any variables, but if is,
120 | // only allow specific keywords (see function for specific keywords)
121 | // Thanks for inspiration: https://github.com/guybedford/es-module-lexer/blob/559a550318fcdfe20c60cb322c147905b5aadf9f/src/lexer.c#L186-L200
122 | if !b[left].is_ascii_alphanumeric()
123 | || is_slash_preceded_by_regex_possible_keyword(&b, left)
124 | {
125 | // mini [] state, anything in [] is literal, so skip / detection
126 | let mut is_in_bracket = false;
127 | let re_closing_pos = match b[i + 1..].iter().enumerate().position(|(j, &v)| {
128 | if v == b'[' && !is_backslash_escaped(b, i + 1 + j) {
129 | is_in_bracket = true;
130 | return false;
131 | } else if v == b']' && !is_backslash_escaped(b, i + 1 + j) {
132 | is_in_bracket = false;
133 | return false;
134 | } else if v == b'\n' {
135 | // TODO: this might be redundant now that i implemented the divisiion / regex heuristic
136 | return true;
137 | } else if !is_in_bracket && v == b'/' && !is_backslash_escaped(b, i + 1 + j) {
138 | return true;
139 | }
140 | false
141 | }) {
142 | Some(pos) => pos,
143 | None => break, // assume reach end of file
144 | };
145 | if b[i + 1 + re_closing_pos] == b'\n' {
146 | // it's a division, not a regex
147 | i += 1;
148 | continue;
149 | } else {
150 | // we also need to skip regex modifiers
151 | let re_modifier_pos = match b[i + 1 + re_closing_pos + 1..]
152 | .iter()
153 | .position(|&v| !v.is_ascii_alphabetic())
154 | {
155 | Some(pos) => pos,
156 | None => break, // assume reach end of file
157 | };
158 | // println!(
159 | // "regex {}",
160 | // &s[i..i + 1 + re_closing_pos + 1 + re_modifier_pos]
161 | // );
162 | i += 1 + re_closing_pos + 1 + re_modifier_pos;
163 | continue;
164 | }
165 | }
166 | }
167 |
168 | let result = cb(b, &mut i, c);
169 | if result == WalkCallbackResult::Break {
170 | break;
171 | } else if result == WalkCallbackResult::Continue {
172 | i += 1;
173 | }
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/src/utils.rs:
--------------------------------------------------------------------------------
1 | // safe index (shorten to not be annoying)
2 | pub fn si(i: usize, b: &[u8]) -> usize {
3 | i.min(b.len() - 1)
4 | }
5 |
6 | // make sure things aren't escaped by backtracking the number of backslashes.
7 | // we consider escaped if has an odd number of backslashes.
8 | pub fn is_backslash_escaped(full_str: &[u8], char_index: usize) -> bool {
9 | let mut prev_iter_pos = char_index - 1;
10 | while full_str[prev_iter_pos] == b'\\' {
11 | if prev_iter_pos > 0 {
12 | prev_iter_pos -= 1;
13 | } else {
14 | break;
15 | }
16 | }
17 | let backslash_num = char_index - prev_iter_pos + 1;
18 | backslash_num % 2 == 1
19 | }
20 |
21 | // make sure the identifier is it itself, e.g. match `import` not `blaimport`.
22 | // this works the same was as regex \b
23 | pub fn is_word_bounded(
24 | full_str: &[u8],
25 | identifier_start_index: usize,
26 | identifier_end_index: usize,
27 | ) -> bool {
28 | let left_bounded = identifier_start_index <= 0
29 | || !full_str[identifier_start_index - 1].is_ascii_alphanumeric();
30 | let right_bounded = identifier_end_index >= full_str.len()
31 | || !full_str[identifier_end_index].is_ascii_alphanumeric();
32 | left_bounded && right_bounded
33 | }
34 |
35 | // walks to the left until a non-whitespace character is found.
36 | // return 0 if out of string bounds
37 | pub fn get_nearest_non_whitespace_index_left(full_str: &[u8], char_index: usize) -> usize {
38 | if char_index <= 0 {
39 | return 0;
40 | }
41 | let mut i = char_index;
42 | while i > 0 {
43 | i -= 1;
44 | if !full_str[i].is_ascii_whitespace() {
45 | break;
46 | }
47 | }
48 | i
49 | }
50 |
51 | // walks to the right until a non-whitespace character is found.
52 | // return string last index if out of string bounds
53 | pub fn get_nearest_non_whitespace_index_right(full_str: &[u8], char_index: usize) -> usize {
54 | if char_index >= full_str.len() - 1 {
55 | return full_str.len() - 1;
56 | }
57 | let mut i = char_index;
58 | loop {
59 | if !full_str[i].is_ascii_whitespace() {
60 | break;
61 | }
62 | if i < full_str.len() - 1 {
63 | i += 1;
64 | } else {
65 | break;
66 | }
67 | }
68 | i
69 | }
70 |
71 | pub fn is_import_identifier(full_str: &[u8], iter_index: usize) -> bool {
72 | full_str[iter_index] == b'i'
73 | && full_str[si(iter_index + 1, full_str)] == b'm'
74 | && full_str[si(iter_index + 2, full_str)] == b'p'
75 | && full_str[si(iter_index + 3, full_str)] == b'o'
76 | && full_str[si(iter_index + 4, full_str)] == b'r'
77 | && full_str[si(iter_index + 5, full_str)] == b't'
78 | && is_word_bounded(&full_str, iter_index, iter_index + 6)
79 | }
80 |
81 | pub fn is_meta_identifier(full_str: &[u8], iter_index: usize) -> bool {
82 | full_str[iter_index] == b'm'
83 | && full_str[si(iter_index + 1, full_str)] == b'e'
84 | && full_str[si(iter_index + 2, full_str)] == b't'
85 | && full_str[si(iter_index + 3, full_str)] == b'a'
86 | && is_word_bounded(&full_str, iter_index, iter_index + 4)
87 | }
88 |
89 | pub fn is_export_identifier(full_str: &[u8], iter_index: usize) -> bool {
90 | full_str[iter_index] == b'e'
91 | && full_str[si(iter_index + 1, full_str)] == b'x'
92 | && full_str[si(iter_index + 2, full_str)] == b'p'
93 | && full_str[si(iter_index + 3, full_str)] == b'o'
94 | && full_str[si(iter_index + 4, full_str)] == b'r'
95 | && full_str[si(iter_index + 5, full_str)] == b't'
96 | && is_word_bounded(&full_str, iter_index, iter_index + 6)
97 | }
98 |
99 | pub fn is_require_identifier(full_str: &[u8], iter_index: usize) -> bool {
100 | full_str[iter_index] == b'r'
101 | && full_str[si(iter_index + 1, full_str)] == b'e'
102 | && full_str[si(iter_index + 2, full_str)] == b'q'
103 | && full_str[si(iter_index + 3, full_str)] == b'u'
104 | && full_str[si(iter_index + 4, full_str)] == b'i'
105 | && full_str[si(iter_index + 5, full_str)] == b'r'
106 | && full_str[si(iter_index + 6, full_str)] == b'e'
107 | && is_word_bounded(full_str, iter_index, iter_index + 7)
108 | }
109 |
110 | pub fn is_module_identifier(full_str: &[u8], iter_index: usize) -> bool {
111 | full_str[iter_index] == b'm'
112 | && full_str[si(iter_index + 1, full_str)] == b'o'
113 | && full_str[si(iter_index + 2, full_str)] == b'd'
114 | && full_str[si(iter_index + 3, full_str)] == b'u'
115 | && full_str[si(iter_index + 4, full_str)] == b'l'
116 | && full_str[si(iter_index + 5, full_str)] == b'e'
117 | && is_word_bounded(full_str, iter_index, iter_index + 6)
118 | }
119 |
120 | pub fn is_exports_identifier(full_str: &[u8], iter_index: usize) -> bool {
121 | full_str[iter_index] == b'e'
122 | && full_str[si(iter_index + 1, full_str)] == b'x'
123 | && full_str[si(iter_index + 2, full_str)] == b'p'
124 | && full_str[si(iter_index + 3, full_str)] == b'o'
125 | && full_str[si(iter_index + 4, full_str)] == b'r'
126 | && full_str[si(iter_index + 5, full_str)] == b't'
127 | && full_str[si(iter_index + 6, full_str)] == b's'
128 | && is_word_bounded(full_str, iter_index, iter_index + 7)
129 | }
130 |
131 | // check if preceded by var, let, const
132 | pub fn is_var_declaration(full_str: &[u8], identifier_start_index: usize) -> bool {
133 | let prev_non_whitespace_index =
134 | get_nearest_non_whitespace_index_left(full_str, identifier_start_index);
135 |
136 | // var
137 | if full_str[prev_non_whitespace_index] == b'r'
138 | && full_str[prev_non_whitespace_index.saturating_sub(1)] == b'a'
139 | && full_str[prev_non_whitespace_index.saturating_sub(2)] == b'v'
140 | && is_word_bounded(
141 | full_str,
142 | prev_non_whitespace_index - 2,
143 | prev_non_whitespace_index + 1,
144 | )
145 | {
146 | return true;
147 | }
148 |
149 | // let
150 | if full_str[prev_non_whitespace_index] == b't'
151 | && full_str[prev_non_whitespace_index.saturating_sub(1)] == b'e'
152 | && full_str[prev_non_whitespace_index.saturating_sub(2)] == b'l'
153 | && is_word_bounded(
154 | full_str,
155 | prev_non_whitespace_index - 2,
156 | prev_non_whitespace_index + 1,
157 | )
158 | {
159 | return true;
160 | }
161 |
162 | // const
163 | if full_str[prev_non_whitespace_index] == b't'
164 | && full_str[prev_non_whitespace_index.saturating_sub(1)] == b's'
165 | && full_str[prev_non_whitespace_index.saturating_sub(2)] == b'n'
166 | && full_str[prev_non_whitespace_index.saturating_sub(3)] == b'o'
167 | && full_str[prev_non_whitespace_index.saturating_sub(4)] == b'c'
168 | && is_word_bounded(
169 | full_str,
170 | prev_non_whitespace_index - 4,
171 | prev_non_whitespace_index + 1,
172 | )
173 | {
174 | return true;
175 | }
176 |
177 | false
178 | }
179 |
180 | // check if it's a function parameter that's creating a scope
181 | pub fn is_function_param_declaration(
182 | full_str: &[u8],
183 | identifier_start_index: usize,
184 | identifier_end_index: usize,
185 | ) -> bool {
186 | let prev_non_whitespace_index =
187 | get_nearest_non_whitespace_index_left(full_str, identifier_start_index);
188 | let next_non_whitespace_index =
189 | get_nearest_non_whitespace_index_right(full_str, identifier_end_index);
190 |
191 | // function(identifier) {}
192 | // function(foo, identifier) {}
193 | // function(foo, identifier, bar) {}
194 | // function(foo = identifier) {}
195 | if full_str[prev_non_whitespace_index] == b'=' {
196 | return false;
197 | }
198 | if full_str[prev_non_whitespace_index] == b'(' || full_str[prev_non_whitespace_index] == b',' {
199 | return true;
200 | }
201 | if full_str[next_non_whitespace_index] == b')' || full_str[next_non_whitespace_index] == b',' {
202 | return true;
203 | }
204 |
205 | // identifier => {}
206 | if full_str[next_non_whitespace_index] == b'='
207 | && full_str[next_non_whitespace_index + 1] == b'>'
208 | {
209 | return true;
210 | }
211 |
212 | false
213 | }
214 |
215 | // whether the identifier is preceded by a regex-possible keyword (in reverse check)
216 | // if, else, return, while, yield
217 | pub fn is_slash_preceded_by_regex_possible_keyword(full_str: &[u8], char_index: usize) -> bool {
218 | if full_str[char_index] == b'f' && full_str[char_index.saturating_sub(1)] == b'i' {
219 | return true;
220 | }
221 | if full_str[char_index] == b'e'
222 | && full_str[char_index.saturating_sub(1)] == b's'
223 | && full_str[char_index.saturating_sub(2)] == b'l'
224 | && full_str[char_index.saturating_sub(3)] == b'e'
225 | {
226 | return true;
227 | }
228 | if full_str[char_index] == b'n'
229 | && full_str[char_index.saturating_sub(1)] == b'r'
230 | && full_str[char_index.saturating_sub(2)] == b'u'
231 | && full_str[char_index.saturating_sub(3)] == b't'
232 | && full_str[char_index.saturating_sub(4)] == b'e'
233 | && full_str[char_index.saturating_sub(5)] == b'r'
234 | {
235 | return true;
236 | }
237 | if full_str[char_index] == b'e'
238 | && full_str[char_index.saturating_sub(1)] == b'l'
239 | && full_str[char_index.saturating_sub(2)] == b'i'
240 | && full_str[char_index.saturating_sub(3)] == b'h'
241 | && full_str[char_index.saturating_sub(4)] == b'w'
242 | {
243 | return true;
244 | }
245 | if full_str[char_index] == b'd'
246 | && full_str[char_index.saturating_sub(1)] == b'l'
247 | && full_str[char_index.saturating_sub(2)] == b'e'
248 | && full_str[char_index.saturating_sub(3)] == b'i'
249 | && full_str[char_index.saturating_sub(3)] == b'y'
250 | {
251 | return true;
252 | }
253 | false
254 | }
255 |
--------------------------------------------------------------------------------
/examples/vite/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite",
3 | "version": "0.0.0",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "vite",
9 | "version": "0.0.0",
10 | "dependencies": {
11 | "fmu": "file:../.."
12 | },
13 | "devDependencies": {
14 | "vite": "^3.0.2"
15 | }
16 | },
17 | "../..": {
18 | "name": "fmu",
19 | "version": "0.0.1",
20 | "license": "MIT",
21 | "funding": {
22 | "url": "https://bjornlu.com/sponsor"
23 | }
24 | },
25 | "node_modules/@esbuild/android-arm": {
26 | "version": "0.15.18",
27 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz",
28 | "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==",
29 | "cpu": [
30 | "arm"
31 | ],
32 | "dev": true,
33 | "optional": true,
34 | "os": [
35 | "android"
36 | ],
37 | "engines": {
38 | "node": ">=12"
39 | }
40 | },
41 | "node_modules/@esbuild/linux-loong64": {
42 | "version": "0.15.18",
43 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz",
44 | "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==",
45 | "cpu": [
46 | "loong64"
47 | ],
48 | "dev": true,
49 | "optional": true,
50 | "os": [
51 | "linux"
52 | ],
53 | "engines": {
54 | "node": ">=12"
55 | }
56 | },
57 | "node_modules/esbuild": {
58 | "version": "0.15.18",
59 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz",
60 | "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==",
61 | "dev": true,
62 | "hasInstallScript": true,
63 | "bin": {
64 | "esbuild": "bin/esbuild"
65 | },
66 | "engines": {
67 | "node": ">=12"
68 | },
69 | "optionalDependencies": {
70 | "@esbuild/android-arm": "0.15.18",
71 | "@esbuild/linux-loong64": "0.15.18",
72 | "esbuild-android-64": "0.15.18",
73 | "esbuild-android-arm64": "0.15.18",
74 | "esbuild-darwin-64": "0.15.18",
75 | "esbuild-darwin-arm64": "0.15.18",
76 | "esbuild-freebsd-64": "0.15.18",
77 | "esbuild-freebsd-arm64": "0.15.18",
78 | "esbuild-linux-32": "0.15.18",
79 | "esbuild-linux-64": "0.15.18",
80 | "esbuild-linux-arm": "0.15.18",
81 | "esbuild-linux-arm64": "0.15.18",
82 | "esbuild-linux-mips64le": "0.15.18",
83 | "esbuild-linux-ppc64le": "0.15.18",
84 | "esbuild-linux-riscv64": "0.15.18",
85 | "esbuild-linux-s390x": "0.15.18",
86 | "esbuild-netbsd-64": "0.15.18",
87 | "esbuild-openbsd-64": "0.15.18",
88 | "esbuild-sunos-64": "0.15.18",
89 | "esbuild-windows-32": "0.15.18",
90 | "esbuild-windows-64": "0.15.18",
91 | "esbuild-windows-arm64": "0.15.18"
92 | }
93 | },
94 | "node_modules/esbuild-android-64": {
95 | "version": "0.15.18",
96 | "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz",
97 | "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==",
98 | "cpu": [
99 | "x64"
100 | ],
101 | "dev": true,
102 | "optional": true,
103 | "os": [
104 | "android"
105 | ],
106 | "engines": {
107 | "node": ">=12"
108 | }
109 | },
110 | "node_modules/esbuild-android-arm64": {
111 | "version": "0.15.18",
112 | "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz",
113 | "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==",
114 | "cpu": [
115 | "arm64"
116 | ],
117 | "dev": true,
118 | "optional": true,
119 | "os": [
120 | "android"
121 | ],
122 | "engines": {
123 | "node": ">=12"
124 | }
125 | },
126 | "node_modules/esbuild-darwin-64": {
127 | "version": "0.15.18",
128 | "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz",
129 | "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==",
130 | "cpu": [
131 | "x64"
132 | ],
133 | "dev": true,
134 | "optional": true,
135 | "os": [
136 | "darwin"
137 | ],
138 | "engines": {
139 | "node": ">=12"
140 | }
141 | },
142 | "node_modules/esbuild-darwin-arm64": {
143 | "version": "0.15.18",
144 | "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz",
145 | "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==",
146 | "cpu": [
147 | "arm64"
148 | ],
149 | "dev": true,
150 | "optional": true,
151 | "os": [
152 | "darwin"
153 | ],
154 | "engines": {
155 | "node": ">=12"
156 | }
157 | },
158 | "node_modules/esbuild-freebsd-64": {
159 | "version": "0.15.18",
160 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz",
161 | "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==",
162 | "cpu": [
163 | "x64"
164 | ],
165 | "dev": true,
166 | "optional": true,
167 | "os": [
168 | "freebsd"
169 | ],
170 | "engines": {
171 | "node": ">=12"
172 | }
173 | },
174 | "node_modules/esbuild-freebsd-arm64": {
175 | "version": "0.15.18",
176 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz",
177 | "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==",
178 | "cpu": [
179 | "arm64"
180 | ],
181 | "dev": true,
182 | "optional": true,
183 | "os": [
184 | "freebsd"
185 | ],
186 | "engines": {
187 | "node": ">=12"
188 | }
189 | },
190 | "node_modules/esbuild-linux-32": {
191 | "version": "0.15.18",
192 | "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz",
193 | "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==",
194 | "cpu": [
195 | "ia32"
196 | ],
197 | "dev": true,
198 | "optional": true,
199 | "os": [
200 | "linux"
201 | ],
202 | "engines": {
203 | "node": ">=12"
204 | }
205 | },
206 | "node_modules/esbuild-linux-64": {
207 | "version": "0.15.18",
208 | "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz",
209 | "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==",
210 | "cpu": [
211 | "x64"
212 | ],
213 | "dev": true,
214 | "optional": true,
215 | "os": [
216 | "linux"
217 | ],
218 | "engines": {
219 | "node": ">=12"
220 | }
221 | },
222 | "node_modules/esbuild-linux-arm": {
223 | "version": "0.15.18",
224 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz",
225 | "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==",
226 | "cpu": [
227 | "arm"
228 | ],
229 | "dev": true,
230 | "optional": true,
231 | "os": [
232 | "linux"
233 | ],
234 | "engines": {
235 | "node": ">=12"
236 | }
237 | },
238 | "node_modules/esbuild-linux-arm64": {
239 | "version": "0.15.18",
240 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz",
241 | "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==",
242 | "cpu": [
243 | "arm64"
244 | ],
245 | "dev": true,
246 | "optional": true,
247 | "os": [
248 | "linux"
249 | ],
250 | "engines": {
251 | "node": ">=12"
252 | }
253 | },
254 | "node_modules/esbuild-linux-mips64le": {
255 | "version": "0.15.18",
256 | "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz",
257 | "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==",
258 | "cpu": [
259 | "mips64el"
260 | ],
261 | "dev": true,
262 | "optional": true,
263 | "os": [
264 | "linux"
265 | ],
266 | "engines": {
267 | "node": ">=12"
268 | }
269 | },
270 | "node_modules/esbuild-linux-ppc64le": {
271 | "version": "0.15.18",
272 | "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz",
273 | "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==",
274 | "cpu": [
275 | "ppc64"
276 | ],
277 | "dev": true,
278 | "optional": true,
279 | "os": [
280 | "linux"
281 | ],
282 | "engines": {
283 | "node": ">=12"
284 | }
285 | },
286 | "node_modules/esbuild-linux-riscv64": {
287 | "version": "0.15.18",
288 | "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz",
289 | "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==",
290 | "cpu": [
291 | "riscv64"
292 | ],
293 | "dev": true,
294 | "optional": true,
295 | "os": [
296 | "linux"
297 | ],
298 | "engines": {
299 | "node": ">=12"
300 | }
301 | },
302 | "node_modules/esbuild-linux-s390x": {
303 | "version": "0.15.18",
304 | "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz",
305 | "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==",
306 | "cpu": [
307 | "s390x"
308 | ],
309 | "dev": true,
310 | "optional": true,
311 | "os": [
312 | "linux"
313 | ],
314 | "engines": {
315 | "node": ">=12"
316 | }
317 | },
318 | "node_modules/esbuild-netbsd-64": {
319 | "version": "0.15.18",
320 | "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz",
321 | "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==",
322 | "cpu": [
323 | "x64"
324 | ],
325 | "dev": true,
326 | "optional": true,
327 | "os": [
328 | "netbsd"
329 | ],
330 | "engines": {
331 | "node": ">=12"
332 | }
333 | },
334 | "node_modules/esbuild-openbsd-64": {
335 | "version": "0.15.18",
336 | "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz",
337 | "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==",
338 | "cpu": [
339 | "x64"
340 | ],
341 | "dev": true,
342 | "optional": true,
343 | "os": [
344 | "openbsd"
345 | ],
346 | "engines": {
347 | "node": ">=12"
348 | }
349 | },
350 | "node_modules/esbuild-sunos-64": {
351 | "version": "0.15.18",
352 | "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz",
353 | "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==",
354 | "cpu": [
355 | "x64"
356 | ],
357 | "dev": true,
358 | "optional": true,
359 | "os": [
360 | "sunos"
361 | ],
362 | "engines": {
363 | "node": ">=12"
364 | }
365 | },
366 | "node_modules/esbuild-windows-32": {
367 | "version": "0.15.18",
368 | "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz",
369 | "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==",
370 | "cpu": [
371 | "ia32"
372 | ],
373 | "dev": true,
374 | "optional": true,
375 | "os": [
376 | "win32"
377 | ],
378 | "engines": {
379 | "node": ">=12"
380 | }
381 | },
382 | "node_modules/esbuild-windows-64": {
383 | "version": "0.15.18",
384 | "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz",
385 | "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==",
386 | "cpu": [
387 | "x64"
388 | ],
389 | "dev": true,
390 | "optional": true,
391 | "os": [
392 | "win32"
393 | ],
394 | "engines": {
395 | "node": ">=12"
396 | }
397 | },
398 | "node_modules/esbuild-windows-arm64": {
399 | "version": "0.15.18",
400 | "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz",
401 | "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==",
402 | "cpu": [
403 | "arm64"
404 | ],
405 | "dev": true,
406 | "optional": true,
407 | "os": [
408 | "win32"
409 | ],
410 | "engines": {
411 | "node": ">=12"
412 | }
413 | },
414 | "node_modules/fmu": {
415 | "resolved": "../..",
416 | "link": true
417 | },
418 | "node_modules/fsevents": {
419 | "version": "2.3.2",
420 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
421 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
422 | "dev": true,
423 | "hasInstallScript": true,
424 | "optional": true,
425 | "os": [
426 | "darwin"
427 | ],
428 | "engines": {
429 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
430 | }
431 | },
432 | "node_modules/function-bind": {
433 | "version": "1.1.1",
434 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
435 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
436 | "dev": true
437 | },
438 | "node_modules/has": {
439 | "version": "1.0.3",
440 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
441 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
442 | "dev": true,
443 | "dependencies": {
444 | "function-bind": "^1.1.1"
445 | },
446 | "engines": {
447 | "node": ">= 0.4.0"
448 | }
449 | },
450 | "node_modules/is-core-module": {
451 | "version": "2.9.0",
452 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz",
453 | "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==",
454 | "dev": true,
455 | "dependencies": {
456 | "has": "^1.0.3"
457 | },
458 | "funding": {
459 | "url": "https://github.com/sponsors/ljharb"
460 | }
461 | },
462 | "node_modules/nanoid": {
463 | "version": "3.3.6",
464 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
465 | "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
466 | "dev": true,
467 | "funding": [
468 | {
469 | "type": "github",
470 | "url": "https://github.com/sponsors/ai"
471 | }
472 | ],
473 | "bin": {
474 | "nanoid": "bin/nanoid.cjs"
475 | },
476 | "engines": {
477 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
478 | }
479 | },
480 | "node_modules/path-parse": {
481 | "version": "1.0.7",
482 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
483 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
484 | "dev": true
485 | },
486 | "node_modules/picocolors": {
487 | "version": "1.0.0",
488 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
489 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
490 | "dev": true
491 | },
492 | "node_modules/postcss": {
493 | "version": "8.4.31",
494 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
495 | "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
496 | "dev": true,
497 | "funding": [
498 | {
499 | "type": "opencollective",
500 | "url": "https://opencollective.com/postcss/"
501 | },
502 | {
503 | "type": "tidelift",
504 | "url": "https://tidelift.com/funding/github/npm/postcss"
505 | },
506 | {
507 | "type": "github",
508 | "url": "https://github.com/sponsors/ai"
509 | }
510 | ],
511 | "dependencies": {
512 | "nanoid": "^3.3.6",
513 | "picocolors": "^1.0.0",
514 | "source-map-js": "^1.0.2"
515 | },
516 | "engines": {
517 | "node": "^10 || ^12 || >=14"
518 | }
519 | },
520 | "node_modules/resolve": {
521 | "version": "1.22.1",
522 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
523 | "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
524 | "dev": true,
525 | "dependencies": {
526 | "is-core-module": "^2.9.0",
527 | "path-parse": "^1.0.7",
528 | "supports-preserve-symlinks-flag": "^1.0.0"
529 | },
530 | "bin": {
531 | "resolve": "bin/resolve"
532 | },
533 | "funding": {
534 | "url": "https://github.com/sponsors/ljharb"
535 | }
536 | },
537 | "node_modules/rollup": {
538 | "version": "2.79.2",
539 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz",
540 | "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
541 | "dev": true,
542 | "bin": {
543 | "rollup": "dist/bin/rollup"
544 | },
545 | "engines": {
546 | "node": ">=10.0.0"
547 | },
548 | "optionalDependencies": {
549 | "fsevents": "~2.3.2"
550 | }
551 | },
552 | "node_modules/source-map-js": {
553 | "version": "1.0.2",
554 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
555 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
556 | "dev": true,
557 | "engines": {
558 | "node": ">=0.10.0"
559 | }
560 | },
561 | "node_modules/supports-preserve-symlinks-flag": {
562 | "version": "1.0.0",
563 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
564 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
565 | "dev": true,
566 | "engines": {
567 | "node": ">= 0.4"
568 | },
569 | "funding": {
570 | "url": "https://github.com/sponsors/ljharb"
571 | }
572 | },
573 | "node_modules/vite": {
574 | "version": "3.2.11",
575 | "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.11.tgz",
576 | "integrity": "sha512-K/jGKL/PgbIgKCiJo5QbASQhFiV02X9Jh+Qq0AKCRCRKZtOTVi4t6wh75FDpGf2N9rYOnzH87OEFQNaFy6pdxQ==",
577 | "dev": true,
578 | "dependencies": {
579 | "esbuild": "^0.15.9",
580 | "postcss": "^8.4.18",
581 | "resolve": "^1.22.1",
582 | "rollup": "^2.79.1"
583 | },
584 | "bin": {
585 | "vite": "bin/vite.js"
586 | },
587 | "engines": {
588 | "node": "^14.18.0 || >=16.0.0"
589 | },
590 | "optionalDependencies": {
591 | "fsevents": "~2.3.2"
592 | },
593 | "peerDependencies": {
594 | "@types/node": ">= 14",
595 | "less": "*",
596 | "sass": "*",
597 | "stylus": "*",
598 | "sugarss": "*",
599 | "terser": "^5.4.0"
600 | },
601 | "peerDependenciesMeta": {
602 | "@types/node": {
603 | "optional": true
604 | },
605 | "less": {
606 | "optional": true
607 | },
608 | "sass": {
609 | "optional": true
610 | },
611 | "stylus": {
612 | "optional": true
613 | },
614 | "sugarss": {
615 | "optional": true
616 | },
617 | "terser": {
618 | "optional": true
619 | }
620 | }
621 | }
622 | },
623 | "dependencies": {
624 | "@esbuild/android-arm": {
625 | "version": "0.15.18",
626 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz",
627 | "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==",
628 | "dev": true,
629 | "optional": true
630 | },
631 | "@esbuild/linux-loong64": {
632 | "version": "0.15.18",
633 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz",
634 | "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==",
635 | "dev": true,
636 | "optional": true
637 | },
638 | "esbuild": {
639 | "version": "0.15.18",
640 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz",
641 | "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==",
642 | "dev": true,
643 | "requires": {
644 | "@esbuild/android-arm": "0.15.18",
645 | "@esbuild/linux-loong64": "0.15.18",
646 | "esbuild-android-64": "0.15.18",
647 | "esbuild-android-arm64": "0.15.18",
648 | "esbuild-darwin-64": "0.15.18",
649 | "esbuild-darwin-arm64": "0.15.18",
650 | "esbuild-freebsd-64": "0.15.18",
651 | "esbuild-freebsd-arm64": "0.15.18",
652 | "esbuild-linux-32": "0.15.18",
653 | "esbuild-linux-64": "0.15.18",
654 | "esbuild-linux-arm": "0.15.18",
655 | "esbuild-linux-arm64": "0.15.18",
656 | "esbuild-linux-mips64le": "0.15.18",
657 | "esbuild-linux-ppc64le": "0.15.18",
658 | "esbuild-linux-riscv64": "0.15.18",
659 | "esbuild-linux-s390x": "0.15.18",
660 | "esbuild-netbsd-64": "0.15.18",
661 | "esbuild-openbsd-64": "0.15.18",
662 | "esbuild-sunos-64": "0.15.18",
663 | "esbuild-windows-32": "0.15.18",
664 | "esbuild-windows-64": "0.15.18",
665 | "esbuild-windows-arm64": "0.15.18"
666 | }
667 | },
668 | "esbuild-android-64": {
669 | "version": "0.15.18",
670 | "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz",
671 | "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==",
672 | "dev": true,
673 | "optional": true
674 | },
675 | "esbuild-android-arm64": {
676 | "version": "0.15.18",
677 | "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz",
678 | "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==",
679 | "dev": true,
680 | "optional": true
681 | },
682 | "esbuild-darwin-64": {
683 | "version": "0.15.18",
684 | "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz",
685 | "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==",
686 | "dev": true,
687 | "optional": true
688 | },
689 | "esbuild-darwin-arm64": {
690 | "version": "0.15.18",
691 | "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz",
692 | "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==",
693 | "dev": true,
694 | "optional": true
695 | },
696 | "esbuild-freebsd-64": {
697 | "version": "0.15.18",
698 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz",
699 | "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==",
700 | "dev": true,
701 | "optional": true
702 | },
703 | "esbuild-freebsd-arm64": {
704 | "version": "0.15.18",
705 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz",
706 | "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==",
707 | "dev": true,
708 | "optional": true
709 | },
710 | "esbuild-linux-32": {
711 | "version": "0.15.18",
712 | "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz",
713 | "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==",
714 | "dev": true,
715 | "optional": true
716 | },
717 | "esbuild-linux-64": {
718 | "version": "0.15.18",
719 | "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz",
720 | "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==",
721 | "dev": true,
722 | "optional": true
723 | },
724 | "esbuild-linux-arm": {
725 | "version": "0.15.18",
726 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz",
727 | "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==",
728 | "dev": true,
729 | "optional": true
730 | },
731 | "esbuild-linux-arm64": {
732 | "version": "0.15.18",
733 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz",
734 | "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==",
735 | "dev": true,
736 | "optional": true
737 | },
738 | "esbuild-linux-mips64le": {
739 | "version": "0.15.18",
740 | "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz",
741 | "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==",
742 | "dev": true,
743 | "optional": true
744 | },
745 | "esbuild-linux-ppc64le": {
746 | "version": "0.15.18",
747 | "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz",
748 | "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==",
749 | "dev": true,
750 | "optional": true
751 | },
752 | "esbuild-linux-riscv64": {
753 | "version": "0.15.18",
754 | "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz",
755 | "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==",
756 | "dev": true,
757 | "optional": true
758 | },
759 | "esbuild-linux-s390x": {
760 | "version": "0.15.18",
761 | "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz",
762 | "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==",
763 | "dev": true,
764 | "optional": true
765 | },
766 | "esbuild-netbsd-64": {
767 | "version": "0.15.18",
768 | "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz",
769 | "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==",
770 | "dev": true,
771 | "optional": true
772 | },
773 | "esbuild-openbsd-64": {
774 | "version": "0.15.18",
775 | "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz",
776 | "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==",
777 | "dev": true,
778 | "optional": true
779 | },
780 | "esbuild-sunos-64": {
781 | "version": "0.15.18",
782 | "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz",
783 | "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==",
784 | "dev": true,
785 | "optional": true
786 | },
787 | "esbuild-windows-32": {
788 | "version": "0.15.18",
789 | "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz",
790 | "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==",
791 | "dev": true,
792 | "optional": true
793 | },
794 | "esbuild-windows-64": {
795 | "version": "0.15.18",
796 | "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz",
797 | "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==",
798 | "dev": true,
799 | "optional": true
800 | },
801 | "esbuild-windows-arm64": {
802 | "version": "0.15.18",
803 | "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz",
804 | "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==",
805 | "dev": true,
806 | "optional": true
807 | },
808 | "fmu": {
809 | "version": "file:../.."
810 | },
811 | "fsevents": {
812 | "version": "2.3.2",
813 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
814 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
815 | "dev": true,
816 | "optional": true
817 | },
818 | "function-bind": {
819 | "version": "1.1.1",
820 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
821 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
822 | "dev": true
823 | },
824 | "has": {
825 | "version": "1.0.3",
826 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
827 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
828 | "dev": true,
829 | "requires": {
830 | "function-bind": "^1.1.1"
831 | }
832 | },
833 | "is-core-module": {
834 | "version": "2.9.0",
835 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz",
836 | "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==",
837 | "dev": true,
838 | "requires": {
839 | "has": "^1.0.3"
840 | }
841 | },
842 | "nanoid": {
843 | "version": "3.3.6",
844 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
845 | "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
846 | "dev": true
847 | },
848 | "path-parse": {
849 | "version": "1.0.7",
850 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
851 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
852 | "dev": true
853 | },
854 | "picocolors": {
855 | "version": "1.0.0",
856 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
857 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
858 | "dev": true
859 | },
860 | "postcss": {
861 | "version": "8.4.31",
862 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
863 | "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
864 | "dev": true,
865 | "requires": {
866 | "nanoid": "^3.3.6",
867 | "picocolors": "^1.0.0",
868 | "source-map-js": "^1.0.2"
869 | }
870 | },
871 | "resolve": {
872 | "version": "1.22.1",
873 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
874 | "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
875 | "dev": true,
876 | "requires": {
877 | "is-core-module": "^2.9.0",
878 | "path-parse": "^1.0.7",
879 | "supports-preserve-symlinks-flag": "^1.0.0"
880 | }
881 | },
882 | "rollup": {
883 | "version": "2.79.2",
884 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz",
885 | "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
886 | "dev": true,
887 | "requires": {
888 | "fsevents": "~2.3.2"
889 | }
890 | },
891 | "source-map-js": {
892 | "version": "1.0.2",
893 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
894 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
895 | "dev": true
896 | },
897 | "supports-preserve-symlinks-flag": {
898 | "version": "1.0.0",
899 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
900 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
901 | "dev": true
902 | },
903 | "vite": {
904 | "version": "3.2.11",
905 | "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.11.tgz",
906 | "integrity": "sha512-K/jGKL/PgbIgKCiJo5QbASQhFiV02X9Jh+Qq0AKCRCRKZtOTVi4t6wh75FDpGf2N9rYOnzH87OEFQNaFy6pdxQ==",
907 | "dev": true,
908 | "requires": {
909 | "esbuild": "^0.15.9",
910 | "fsevents": "~2.3.2",
911 | "postcss": "^8.4.18",
912 | "resolve": "^1.22.1",
913 | "rollup": "^2.79.1"
914 | }
915 | }
916 | }
917 | }
918 |
--------------------------------------------------------------------------------