├── 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 | Sponsors 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 | --------------------------------------------------------------------------------