Showing preview only (1,167K chars total). Download the full file or copy to clipboard to get everything.
Repository: remorses/bundless
Branch: master
Commit: c7c167a78b03
Files: 422
Total size: 1.0 MB
Directory structure:
gitextract_cw0f3mmf/
├── .changeset/
│ ├── README.md
│ └── config.json
├── .github/
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── .prettierrc
├── .vscode/
│ └── settings.json
├── README.md
├── TODOS.md
├── bundless/
│ ├── CHANGELOG.md
│ ├── bin.js
│ ├── package.json
│ ├── src/
│ │ ├── build/
│ │ │ └── index.ts
│ │ ├── cli.ts
│ │ ├── client/
│ │ │ ├── template.ts
│ │ │ └── types.ts
│ │ ├── config.ts
│ │ ├── constants.ts
│ │ ├── hmr-graph.ts
│ │ ├── index.ts
│ │ ├── logger.ts
│ │ ├── middleware/
│ │ │ ├── history-fallback.ts
│ │ │ ├── index.ts
│ │ │ ├── open-in-editor.ts
│ │ │ ├── plugins.ts
│ │ │ ├── sourcemap.ts
│ │ │ └── static-serve.ts
│ │ ├── plugins/
│ │ │ ├── assets.ts
│ │ │ ├── buffer.ts
│ │ │ ├── css.ts
│ │ │ ├── env.ts
│ │ │ ├── esbuild.ts
│ │ │ ├── hmr-client.ts
│ │ │ ├── html-ingest.ts
│ │ │ ├── html-resolver.ts
│ │ │ ├── html-transform.ts
│ │ │ ├── index.ts
│ │ │ ├── json.ts
│ │ │ ├── resolve-sourcemaps.ts
│ │ │ ├── rewrite/
│ │ │ │ ├── __snapshots__/
│ │ │ │ │ └── commonjs.test.ts.snap
│ │ │ │ ├── commonjs.test.ts
│ │ │ │ ├── commonjs.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── rewrite.ts
│ │ │ ├── source-map-support.ts
│ │ │ └── url-resolver.ts
│ │ ├── plugins-executor.ts
│ │ ├── prebundle/
│ │ │ ├── __snapshots__/
│ │ │ │ └── prebundle.test.ts.snap
│ │ │ ├── esbuild.ts
│ │ │ ├── index.ts
│ │ │ ├── prebundle.test.ts
│ │ │ ├── prebundle.ts
│ │ │ ├── stats.ts
│ │ │ ├── support.ts
│ │ │ └── traverse.ts
│ │ ├── serve.ts
│ │ └── utils/
│ │ ├── index.ts
│ │ ├── path.test.ts
│ │ ├── path.ts
│ │ ├── profiling.test.ts
│ │ ├── profiling.ts
│ │ ├── sourcemaps.ts
│ │ └── utils.ts
│ └── tsconfig.json
├── examples/
│ ├── react-javascript/
│ │ ├── .gitignore
│ │ ├── bundless.config.js
│ │ ├── package.json
│ │ ├── public/
│ │ │ └── index.html
│ │ └── src/
│ │ ├── app.jsx
│ │ ├── index.jsx
│ │ └── styles.css
│ ├── react-typescript/
│ │ ├── .gitignore
│ │ ├── bundless.config.js
│ │ ├── package.json
│ │ ├── public/
│ │ │ └── index.html
│ │ ├── src/
│ │ │ ├── app.tsx
│ │ │ ├── index.tsx
│ │ │ └── styles.css
│ │ └── tsconfig.json
│ ├── svelte/
│ │ ├── .gitignore
│ │ ├── bundless.config.js
│ │ ├── package.json
│ │ ├── public/
│ │ │ └── index.html
│ │ ├── scripts/
│ │ │ └── setupTypeScript.js
│ │ └── src/
│ │ ├── App.svelte
│ │ ├── global.css
│ │ └── main.js
│ └── vanilla-javascript/
│ ├── .gitignore
│ ├── bundless.config.js
│ ├── package.json
│ ├── public/
│ │ └── index.html
│ └── src/
│ ├── index.js
│ └── styles.css
├── fixtures/
│ ├── html-page/
│ │ ├── __mirror__/
│ │ │ └── index.html
│ │ ├── __snapshots__
│ │ └── index.html
│ ├── outsider.js
│ ├── resolve-sourcemap/
│ │ ├── __mirror__/
│ │ │ ├── folder/
│ │ │ │ └── main.js
│ │ │ └── index.html
│ │ ├── __snapshots__
│ │ ├── folder/
│ │ │ └── main.js
│ │ └── index.html
│ ├── serve-outside-root/
│ │ ├── __mirror__/
│ │ │ ├── __..__/
│ │ │ │ └── outsider.js
│ │ │ ├── index.html
│ │ │ └── main.js
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ └── main.js
│ ├── simple-js/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ └── main.js
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ └── main.js
│ ├── with-alias-plugin/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ ├── main.tsx
│ │ │ └── text.ts
│ │ ├── __snapshots__
│ │ ├── bundless.config.js
│ │ ├── index.html
│ │ ├── main.tsx
│ │ ├── package.json
│ │ └── text.ts
│ ├── with-assets-imports/
│ │ ├── __mirror__/
│ │ │ ├── dynamic-import.js
│ │ │ ├── file.css.cssjs
│ │ │ ├── index.html
│ │ │ └── main.js
│ │ ├── __snapshots__
│ │ ├── bundless.config.js
│ │ ├── dynamic-import.js
│ │ ├── file.css
│ │ ├── index.html
│ │ └── main.js
│ ├── with-babel-plugin/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ └── main.tsx
│ │ ├── __snapshots__
│ │ ├── bundless.config.js
│ │ ├── index.html
│ │ ├── main.tsx
│ │ └── package.json
│ ├── with-commonjs-transform/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ └── main.jsx
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ ├── main.jsx
│ │ └── package.json
│ ├── with-css/
│ │ ├── __mirror__/
│ │ │ ├── file.css.cssjs
│ │ │ ├── file.js
│ │ │ ├── index.html
│ │ │ └── main.js
│ │ ├── __snapshots__
│ │ ├── file.css
│ │ ├── file.js
│ │ ├── index.html
│ │ └── main.js
│ ├── with-css-modules/
│ │ ├── __mirror__/
│ │ │ ├── file.js
│ │ │ ├── file.module.css.cssjs
│ │ │ ├── index.html
│ │ │ └── main.js
│ │ ├── __snapshots__
│ │ ├── file.js
│ │ ├── file.module.css
│ │ ├── index.html
│ │ └── main.js
│ ├── with-custom-assets/
│ │ ├── __mirror__/
│ │ │ ├── file.fakecss.cssjs
│ │ │ ├── file.fakejs
│ │ │ ├── file.js
│ │ │ ├── index.html
│ │ │ ├── main.js
│ │ │ └── x.DAC
│ │ ├── __snapshots__
│ │ ├── bundless.config.js
│ │ ├── file.fakecss
│ │ ├── file.fakejs
│ │ ├── file.js
│ │ ├── index.html
│ │ ├── main.js
│ │ └── x.DAC
│ ├── with-dependencies/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ └── main.js
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ └── main.js
│ ├── with-dependencies-assets/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ └── main.js
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ ├── main.js
│ │ └── package.json
│ ├── with-dynamic-import/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ ├── main.js
│ │ │ └── text.js
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ ├── main.js
│ │ └── text.js
│ ├── with-env-plugin/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ └── main.tsx
│ │ ├── __snapshots__
│ │ ├── bundless.config.js
│ │ ├── envfile
│ │ ├── index.html
│ │ └── main.tsx
│ ├── with-esbuild-plugins/
│ │ ├── __mirror__/
│ │ │ ├── fake.js
│ │ │ ├── file.gql
│ │ │ ├── index.html
│ │ │ └── main.js
│ │ ├── __snapshots__
│ │ ├── bundless.config.js
│ │ ├── fake.js
│ │ ├── file.gql
│ │ ├── index.html
│ │ └── main.js
│ ├── with-imports/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ ├── main.js
│ │ │ └── text.js
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ ├── main.js
│ │ └── text.js
│ ├── with-json/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ ├── main.js
│ │ │ └── text.json
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ ├── main.js
│ │ └── text.json
│ ├── with-linked-workspace/
│ │ ├── __mirror__/
│ │ │ ├── __..__/
│ │ │ │ └── with-many-dependencies/
│ │ │ │ └── main.js
│ │ │ ├── index.html
│ │ │ └── main.js
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ ├── main.js
│ │ └── package.json
│ ├── with-links/
│ │ ├── __mirror__/
│ │ │ └── index.html
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ ├── public/
│ │ │ ├── manifest.json
│ │ │ └── styles1.css
│ │ └── styles2.css
│ ├── with-many-dependencies/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ └── main.js
│ │ ├── __snapshots__
│ │ ├── bundless.config.js
│ │ ├── index.html
│ │ ├── main.js
│ │ └── package.json
│ ├── with-many-entries/
│ │ ├── __mirror__/
│ │ │ ├── a/
│ │ │ │ ├── index.html
│ │ │ │ ├── main.css.cssjs
│ │ │ │ └── main.js
│ │ │ ├── b/
│ │ │ │ ├── index.html
│ │ │ │ ├── main.js
│ │ │ │ └── text.js
│ │ │ └── common.css.cssjs
│ │ ├── __snapshots__
│ │ ├── a/
│ │ │ ├── index.html
│ │ │ ├── main.css
│ │ │ └── main.js
│ │ ├── b/
│ │ │ ├── index.html
│ │ │ ├── main.js
│ │ │ └── text.js
│ │ ├── bundless.config.js
│ │ └── common.css
│ ├── with-node-polyfills/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ ├── main.js
│ │ │ └── path
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ └── main.js
│ ├── with-sourcemaps/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ ├── js.js
│ │ │ ├── main.ts
│ │ │ └── text.js
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ ├── js.js
│ │ ├── main.ts
│ │ ├── package.json
│ │ └── text.js
│ ├── with-svelte/
│ │ ├── App.svelte
│ │ ├── __mirror__/
│ │ │ ├── App.svelte
│ │ │ ├── App.svelte.css
│ │ │ ├── index.html
│ │ │ └── main.js
│ │ ├── __snapshots__
│ │ ├── bundless.config.js
│ │ ├── index.html
│ │ ├── main.js
│ │ └── package.json
│ ├── with-tsconfig-paths/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ ├── main.tsx
│ │ │ └── text.ts
│ │ ├── __snapshots__
│ │ ├── bundless.config.js
│ │ ├── index.html
│ │ ├── main.tsx
│ │ ├── package.json
│ │ └── text.ts
│ ├── with-tsx/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ ├── main.tsx
│ │ │ ├── text.ts
│ │ │ └── utils.ts
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ ├── main.tsx
│ │ ├── text.ts
│ │ └── utils.ts
│ ├── with-typescript/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ ├── main.ts
│ │ │ ├── text.ts
│ │ │ └── utils.ts
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ ├── main.ts
│ │ ├── text.ts
│ │ └── utils.ts
│ └── with-yarn-berry-paths/
│ ├── __mirror__/
│ │ ├── index.html
│ │ └── main.js
│ ├── __snapshots__
│ ├── index.html
│ └── main.js
├── hmr-test-app/
│ ├── __snapshots__/
│ │ ├── bundless
│ │ ├── snowpack
│ │ └── vite
│ ├── bundless.config.js
│ ├── index.test.ts
│ ├── package.json
│ ├── public/
│ │ ├── bundless/
│ │ │ └── index.html
│ │ ├── index.html
│ │ ├── snowpack/
│ │ │ └── index.html
│ │ └── vite/
│ │ └── index.html
│ ├── snowpack.config.js
│ ├── src/
│ │ ├── bridge.jsx
│ │ ├── file.css
│ │ ├── file.json
│ │ ├── file.jsx
│ │ ├── file.module.css
│ │ ├── file2.js
│ │ ├── imported-many-times.js
│ │ └── main.jsx
│ ├── tsconfig.json
│ └── vite.config.js
├── jest.config.js
├── package.json
├── paged/
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── package.json
│ ├── src/
│ │ ├── client/
│ │ │ ├── context.ts
│ │ │ └── index.ts
│ │ ├── constants.ts
│ │ ├── export.tsx
│ │ ├── index.tsx
│ │ ├── plugin.tsx
│ │ ├── routes.ts
│ │ └── server.tsx
│ └── tsconfig.json
├── plugins/
│ ├── alias/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── babel/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── react-refresh/
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── svelte/
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ └── typescript.ts
│ │ ├── tsconfig.json
│ │ └── yarn-error.log
│ └── tsconfig-paths/
│ ├── package.json
│ ├── src/
│ │ └── index.ts
│ └── tsconfig.json
├── scripts/
│ ├── analyze.ts
│ ├── index.html
│ ├── partition.ts
│ ├── scc.ts
│ ├── topological.ts
│ ├── tsconfig.json
│ └── ws.ts
├── tests/
│ ├── CHANGELOG.md
│ ├── fixtures.test.ts
│ ├── package.json
│ └── utils.ts
├── tsconfig.base.json
├── website/
│ ├── .gitignore
│ ├── components/
│ │ └── GradientBg.tsx
│ ├── constants.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── pages/
│ │ ├── _app.tsx
│ │ ├── docs/
│ │ │ ├── benchmarks.mdx
│ │ │ ├── cli.mdx
│ │ │ ├── config.mdx
│ │ │ ├── faq.mdx
│ │ │ ├── how-it-works.mdx
│ │ │ ├── index.mdx
│ │ │ ├── integrations/
│ │ │ │ ├── alias.mdx
│ │ │ │ ├── babel.mdx
│ │ │ │ └── react-refresh.mdx
│ │ │ └── migration.mdx
│ │ └── index.tsx
│ └── tsconfig.json
└── with-pages/
├── CHANGELOG.md
├── components.tsx
├── export.js
├── index.test.ts
├── package.json
├── pages/
│ ├── about.tsx
│ ├── dynamic-import.tsx
│ ├── folder/
│ │ ├── about.tsx
│ │ └── index.tsx
│ ├── index.tsx
│ └── slugs/
│ ├── [slug].tsx
│ └── all/
│ └── [...slugs].tsx
├── rpc/
│ └── example.ts
└── server.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .changeset/README.md
================================================
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/master/docs/common-questions.md)
================================================
FILE: .changeset/config.json
================================================
{
"$schema": "https://unpkg.com/@changesets/config@1.4.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"linked": [],
"access": "public",
"baseBranch": "master",
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"onlyUpdatePeerDependentsWhenOutOfRange": true
},
"updateInternalDependencies": "patch"
}
================================================
FILE: .github/workflows/ci.yml
================================================
name: Npm Package
on:
push:
branches:
- master
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os:
- macos-latest
- windows-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-node@v1
with:
node-version: 12
registry-url: https://registry.npmjs.org/
# caching
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-npm-
restore-keys: |
${{ runner.os }}-npm-
# scripts
- run: yarn
- run: yarn ultra --rebuild -r --filter '@bundless/*' build
- run: yarn test tests && yarn test ./bundless/src
- run: git diff
# - name: Create Release
# id: changesets
# uses: changesets/action@master
# with:
# publish: yarn release
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
================================================
FILE: .gitignore
================================================
node_modules
dist
esm
.DS_Store
*.tsbuildinfo
.ultra.cache.json
**/web_modules/deps_hash
**/web_modules/**/**.js
**/web_modules/**/**.js.map
out
temp
fixtures/with-pages/node_dist
*_out
with-pages/web_modules
with-pages/out
*_dist
_hmr_client.js
.bundless
scripts/metafile.json
================================================
FILE: .prettierrc
================================================
{
"arrowParens": "always",
"jsxSingleQuote": true,
"tabWidth": 4,
"semi": false,
"singleQuote": true,
"trailingComma": "all"
}
================================================
FILE: .vscode/settings.json
================================================
{
"todo-tree.filtering.excludeGlobs": [
"**/web_modules/**",
"**/web_modules/**",
"**/fixtures/**"
]
}
================================================
FILE: README.md
================================================
<div align='center'>
<br/>
<br/>
<h3>bundless</h3>
<p>Next gen dev server and bundler</p>
<p>this project was a Vite alternative with many improvements like plugins, monorepo support, etc, most of them were added back to Vite 2, use Vite instead</p>
<br/>
</div>
# Features
- 10x faster than traditional bundlers
- Error panel with sourcemap support
- jsx, typescript out of the box
- import assets, import css
### What's the difference with traditional tools like Webpack?
- Faster dev server times and faster build speeds (thanks to [esbuild](https://esbuild.github.io))
- Bundless serves native ES modules to the browser, removing the overhead of parsing each module before serving
- Bundless uses a superset of [esbuild plugin system](https://esbuild.github.io/plugins/) to let users enrich its capabilities
### What's the difference with tools like vite?
Bundless is very similar to vite, both serve native es modules to the browser and build a bundled version for production.
Also both are based on a plugin system that can be shared between the dev server and the bundler.
Some differences are:
- Bundless uses the esbuild plugin system instead of rollup
- Bundless uses esbuild instead of rollup for the production bundle
- Bundless still lacks some features like css modules (depends on [esbuild](https://github.com/evanw/esbuild/issues/20)) and more framework support (coming soon)
================================================
FILE: TODOS.md
================================================
- fix stack trace parsing in client, use https://github.com/marvinhagemeister/errorstacks
- make node polyfills an optional plugins list, but include it by default on default config
- add support for multiple errors in error panel
- check that inline sourcemaps are used by esbuild transformer
- add crypto polyfill
- when there is an error and using HMR, do not refresh, instead try to run react refresh and see if it works
- ~~use data url for loading svgs~~
- ~~resolved paths that map from a real file to a fake file won't receive HMR updates because there is no way to resolve them during file change~~
- ~~make a config for assetExtensions, to let user import any file and return its path~~
- ~~put the onResolve function in the plugins executor, this way it does not depend on the presence of node-resolve plugin~~
- ~~replace node-resolve in the traversal with bare imports plugin~~
- ~~add a way to order plugins after or before the builtin plugins~~
- ~~do not rely on the node resolve package for anything, add an additional plugin and add node-resolve only when in yarn pnp~~
- ~~replace external but in meta with a dummy plugin that registers imports~~
- ~~do not run esbuild transform if loader is already js~~
- ~~run all user plugins first, make react refresh use the js loader as output~~
- only use sourcemaps on user packages, npm packages seem to not publish src directory
- ~~dynamic imports should not reorder exports, depend on esbuild~~
- add warning for multiple node modules paths for same package when this package is peer of something
- remove require warnings from paged (only use require when platform is node)
- ~~investigate if using new extensions in a plugins require you to add a resolver, maybe add a universal resolver that resolves all extensions (if they are present in the import path)~~
- think about core feature for bundless for promotion in twitter (esbuild plugins, benchmarks, ssr, meta framework, build speed, monorepo support, hmr fixes, multiple entrypoints,)
- how to make project sustainable? offer migration support for react-scripts and stuff like that?
- ~~makes bundless internal stuff paths start with .bundless, makes easier to analyze network requests~~
- ~~put everything inside .bundless, make this directory path configurable, this way tools like vitro can use .vitro~~
- use basePath to change the index.html page relative urls, this way there is no need to %PUBLIC_URL% need, / -> /base-path/
- more tests for hmr, using puppeteer
- test sourcemaps are correct, throwing errors and checking the browser error line
- test the html entries resolution (public, name.html, html paths in entries, ...)
- implement postcss processing to enable sass, tailwind, ...
================================================
FILE: bundless/CHANGELOG.md
================================================
# @bundless/cli
## 0.6.0
### Minor Changes
- Fixed problems with yarn berry and missing prebundled packages, better console messages
## 0.5.1
### Patch Changes
- Fix dead lock when not passing entries during prebundle
## 0.5.0
### Minor Changes
- Implemented immutable cache for all files, much faster refresh speed
## 0.4.0
### Minor Changes
- Cache dependencies, fix NODE_ENV variable always in production when prebundling
## 0.3.0
### Minor Changes
- Many improvements
## 0.2.6
### Patch Changes
- Updated esbuild
## 0.2.5
### Patch Changes
- Added support for importableAssetsExtensions
## 0.2.4
### Patch Changes
- 717a68e: Fix npm release, removed bin
## 0.2.3
### Patch Changes
- bd7ed34: Added enforce option to plugins
## 0.2.2
### Patch Changes
- 709ef96: Fix define assignments in client template
## 0.2.1
### Patch Changes
- ca42b40: Fix define runtime error in client code
## 0.2.0
### Minor Changes
- 9a0b4e5: Do not use esbuild when loader is js, inject defines in window
## 0.1.9
### Patch Changes
- 0c5c9b2: Store web_modules inside .bundless
## 0.1.8
### Patch Changes
- bbbd527: Bump
## 0.1.7
### Patch Changes
- 325516d: rename dotdot encondig to **..**
- f7684e8: Added basepath support
## 0.1.6
### Patch Changes
- 3541033: Added includeWorkspacePackages option
## 0.1.5
### Patch Changes
- 2e6022f: Small improvements
## 0.1.4
### Patch Changes
- 9c57b90: Better build logs
## 0.1.3
### Patch Changes
- 1b976b6: Less noise in logs, prebundle at start
## 0.1.2
### Patch Changes
- 410f40a: Better logs on nonResolved
## 0.1.1
### Patch Changes
- 7eaff10: Export babelParserOptions
## 0.1.0
### Minor Changes
- 81c8e26: First release
================================================
FILE: bundless/bin.js
================================================
#!/usr/bin/env node
require('./dist/cli')
================================================
FILE: bundless/package.json
================================================
{
"name": "@bundless/cli",
"version": "0.6.0",
"description": "",
"main": "dist/index.js",
"module": "esm/index.js",
"types": "dist/index.d.ts",
"repository": "https://github.com/remorses/esbuild-plugins.git",
"scripts": {
"build": "tsc && tsc -m esnext --outDir esm",
"watch:esm": "tsc -w -m esnext --outDir esm",
"watch:cjs": "tsc -w",
"watch": "run-p watch:esm watch:cjs",
"local": "yarn publish --force --registry http://localhost:4873 --access restricted --no-git-tag-version --patch --message 'Local registry publish'",
"cli": "node bin.js"
},
"files": [
"dist",
"src",
"esm",
"bin.js"
],
"bin": {
"bundless": "bin.js"
},
"keywords": [],
"author": "Tommaso De Rossi, morse <beats.by.morse@gmail.com>",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.12.10",
"@types/chokidar": "^2.1.3",
"@types/dotenv": "^8.2.0",
"@types/es-module-lexer": "^0.3.0",
"@types/find-up": "^4.0.0",
"@types/fs-extra": "^9.0.5",
"@types/koa": "^2.11.6",
"@types/koa-send": "^4.1.2",
"@types/node": "^14.14.13",
"@types/prompts": "^2.0.9",
"@types/qs": "^6.9.5",
"@types/ws": "^7.4.0",
"@types/yargs": "^15.0.11",
"npm-run-all": "^4.1.5",
"qs": "^6.9.4"
},
"dependencies": {
"@babel/parser": "^7.12.11",
"@babel/types": "^7.12.10",
"@esbuild-plugins/all": "^0.0.27",
"@koa/cors": "^3.1.0",
"chalk": "^4.1.0",
"chokidar": "^3.5.1",
"deepmerge": "^4.2.2",
"degit": "^2.8.0",
"dotenv": "^8.2.0",
"dotenv-expand": "^5.1.0",
"@esbuild-plugins/node-globals-polyfill": "^0.1.0",
"es-module-lexer": "^0.3.26",
"esbuild": "^0.11.3",
"escape-string-regexp": "^4.0.0",
"find-up": "^5.0.0",
"fromentries": "^1.3.2",
"fs-extra": "^9.0.1",
"get-port-please": "^2.1.0",
"hash-sum": "^2.0.0",
"koa": "^2.13.0",
"koa-etag": "^4.0.0",
"koa-send": "^5.0.1",
"launch-editor": "^2.2.1",
"lodash": "^4.17.20",
"lru-cache": "^6.0.0",
"magic-string": "^0.25.7",
"merge-source-map": "^1.1.0",
"micro-memoize": "^4.0.9",
"mime-types": "^2.1.29",
"ora": "^5.2.0",
"picomatch": "^2.2.2",
"posthtml": "^0.15.1",
"prompts": "^2.4.0",
"qs": "^6.9.4",
"simple-statistics": "^7.4.0",
"slash": "^3.0.0",
"source-map": "^0.7.3",
"source-map-support": "^0.5.19",
"strip-ansi": "^6.0.0",
"tmpfile": "^0.2.0",
"ws": "^7.4.1",
"yargs": "^16.2.0"
},
"peerDependencies": {}
}
================================================
FILE: bundless/src/build/index.ts
================================================
import deepmerge from 'deepmerge'
import * as esbuild from 'esbuild'
import fromEntries from 'fromentries'
import fs from 'fs-extra'
import path from 'path'
import { Plugin } from '../plugins-executor'
import posthtml, { Node } from 'posthtml'
import slash from 'slash'
import { Config, defaultConfig, getEntries, normalizeConfig } from '../config'
import { MAIN_FIELDS } from '../constants'
import { Logger } from '../logger'
import * as plugins from '../plugins'
import { PluginsExecutor } from '../plugins-executor'
import {
commonEsbuildOptions,
generateDefineObject,
metafileToBundleMap,
metafileToStats,
defaultResolvableExtensions,
} from '../prebundle/esbuild'
import { printStats } from '../prebundle/stats'
import { isUrl, runFunctionOnPaths, stripColon } from '../prebundle/support'
import { metaToTraversalResult } from '../prebundle/traverse'
import {
cleanUrl,
computeDuration,
osAgnosticPath,
partition,
removeLeadingSlash,
} from '../utils'
interface OwnArgs {
logger?: Logger
incremental?: boolean
}
// how to get entrypoints? to support multi entry i should let the user pass them, for the single entry i can just get public/index.html or index.html
// TODO add watch feature for build
export async function build({
logger = new Logger(),
incremental,
...config
}: Config & OwnArgs): Promise<{
bundleMap
traversalGraph
rebuild?: esbuild.BuildInvalidate
}> {
config = normalizeConfig(config)
const {
minify = false,
outDir = 'out',
jsTarget = 'es2018',
basePath = '/',
} = config.build || {}
const startTime = Date.now()
const { platform = 'browser', root = '' } = config
const isBrowser = platform === 'browser'
const userPlugins = config.plugins || []
await fs.remove(outDir)
await fs.ensureDir(outDir)
const publicDir = path.resolve(root, 'public')
const esbuildCwd = process.cwd()
if (fs.existsSync(publicDir)) {
await fs.copy(publicDir, outDir)
}
const mainFields = isBrowser ? MAIN_FIELDS : ['main', 'module']
const initialOptions: esbuild.BuildOptions = {
...commonEsbuildOptions(config),
incremental,
metafile: true,
logLevel: 'warning',
bundle: true,
platform,
target: jsTarget,
publicPath: basePath,
splitting: isBrowser,
// external: externalPackages,
minifyIdentifiers: Boolean(minify),
minifySyntax: Boolean(minify),
minifyWhitespace: Boolean(minify),
mainFields,
define: {
...generateDefineObject({ config, platform, isProd: true }),
},
// tsconfig: tsconfigTempFile,
format: isBrowser ? 'esm' : 'cjs',
write: true,
outdir: outDir,
minify: Boolean(minify),
}
const allPlugins: Plugin[] = [
...userPlugins,
plugins.HtmlResolverPlugin(),
plugins.HtmlIngestPlugin({
root,
name: 'html-ingest',
transformImportPath: cleanUrl,
}),
plugins.UrlResolverPlugin(),
plugins.NodeResolvePlugin({
name: 'node-resolve',
onNonResolved: (p) => {
// throw new Error(`Cannot resolve '${p}'`)
},
onResolved: (p) => {
if (platform !== 'node') {
return
}
// needed for linked workspaces
// const isOutside = path.relative(root, p).startsWith('..')
// TODO should i bundle linked dependencies in ssr build?
if (p.endsWith('.js') && p.includes('node_modules')) {
return {
path: p,
external: true,
}
}
},
mainFields,
extensions: [
...defaultResolvableExtensions,
...(Object.keys(config.loader || {}) || []),
],
}),
...(isBrowser
? [
plugins.NodeModulesPolyfillPlugin(),
plugins.NodeGlobalsPolyfillPlugin({
buffer: true,
process: true,
define: initialOptions.define,
}),
]
: []),
// html ingest should override other html plugins in build, this is because html is transformed to js
]
const pluginsExecutor = new PluginsExecutor({
plugins: allPlugins,
initialOptions,
isProfiling: config.printStats,
ctx: { config, isBuild: true, root },
})
const initialEntries = await getEntries(pluginsExecutor, config)
const entryPoints = await Promise.all(
initialEntries.map(async (x) => {
const resolved = await pluginsExecutor.resolve({
path: x,
resolveDir: root,
})
if (!resolved || !resolved.path) {
throw new Error(`Cannot resolve entry ${x} with plugins`)
}
return resolved.path
}),
)
logger.log(
`Building with esbuild ${entryPoints
.filter((f) => fs.existsSync(f))
.map((x) => osAgnosticPath(x, root))
.join(', ')}\n`,
)
let { rebuild, metafile } = await esbuild.build({
...initialOptions,
entryPoints,
plugins: pluginsExecutor.esbuildPlugins(),
})
let meta: esbuild.Metafile = metafile!
if (config.printStats && !logger.silent) {
console.info(pluginsExecutor.printProfilingResult())
}
logger.debug('finished esbuild build')
meta = runFunctionOnPaths(meta!, (p) => {
p = stripColon(p) // namespace:/path/to/file -> /path/to/file
return p
})
const bundleMap = metafileToBundleMap({
esbuildCwd,
meta,
root,
})
const traversalGraph = await metaToTraversalResult({
meta,
entryPoints,
root,
esbuildCwd,
})
// no outputs?
if (!Object.keys(bundleMap).length) {
return {
bundleMap,
traversalGraph,
rebuild: rebuild,
}
}
const cssToPreload: Record<string, string[]> = fromEntries(
entryPoints.map((x) => osAgnosticPath(x, root)).map((k) => [k, []]),
)
// find all the css files, for every entry file traverse its imports and collect all css files, add the css outputs to cssToInject
for (let entry of entryPoints.map((x) => osAgnosticPath(x, root))) {
traverseGraphDown({
entryPoints: [entry],
traversalGraph,
onNode(imported) {
if (cleanUrl(imported).endsWith('.css')) {
const abs = path.resolve(root, imported)
let output = Object.keys(meta.outputs).find((x) => {
if (!x.endsWith('.css')) {
return
}
const info = meta.outputs[x]
const absInputs = new Set(
Object.keys(info.inputs).map((x) =>
path.resolve(esbuildCwd, x),
),
)
if (absInputs.has(abs)) {
return true
}
})
if (!output) {
throw new Error(`Cannot find output for '${imported}'`)
}
output = path.resolve(esbuildCwd, output)
cssToPreload[entry].push(output)
}
},
})
}
// TODO remove complete css injection after esbuild has css code splitting via js
const cssToInject = Object.keys(meta.outputs).filter((x) =>
x.endsWith('.css'),
)
// needed to run the onTransform on html entries
const htmlPluginsExecutor = new PluginsExecutor({
initialOptions: initialOptions,
plugins: [plugins.HtmlResolverPlugin(), ...userPlugins],
ctx: pluginsExecutor.ctx,
})
for (let entry of entryPoints) {
if (path.extname(entry) === '.html') {
const relativePath = osAgnosticPath(entry, root)
if (!bundleMap[relativePath]) {
throw new Error(
`Cannot find output for '${relativePath}' in ${JSON.stringify(
bundleMap,
null,
4,
)}`,
)
}
let outputJs = path.resolve(root, bundleMap[relativePath]!)
// let outputHtmlPath = path.resolve(
// root,
// path.dirname(bundleMap[relativePath]!),
// path.basename(entry),
// )
// await fs.copyFile(entry, outputHtmlPath)
const {
contents: html = '',
} = await htmlPluginsExecutor.resolveLoadTransform({ path: entry })
if (!html) {
throw new Error(`Cannot load html for ${entry}`)
}
const transformer = posthtml(
[
(tree) => {
// remove previous script tags
tree.walk((node) => {
if (
node &&
node.tag === 'script' &&
node.attrs &&
node.attrs['type'] === 'module' &&
node.attrs['src'] &&
!isUrl(node.attrs['src'])
) {
// TODO maybe leave script tags that are not resolved by plugin executor, maybe they are loaded from some cdn or who knows what, resolver should be able to resolve relative urls
node.tag = false as any
node.content = []
}
return node
})
// add new output files back to html
tree.match({ tag: 'body' }, (node) => {
const jsSrc = path.posix.join(
basePath,
slash(path.relative(outDir, outputJs)),
)
node.content = [
MyNode({
tag: 'script',
attrs: { type: 'module', src: jsSrc },
}),
...(node.content || []),
]
return node
})
// insert head if missing
if (!/<head\b/.test(html)) {
if (/<html\b/.test(html)) {
tree.match({ tag: 'html' }, (html) => {
html.content = insertAfterStrings(
html.content,
MyNode({ tag: 'head', content: [] }),
)
return html
})
} else {
if (Array.isArray(tree)) {
tree = Object.assign(
tree,
insertAfterStrings(
tree,
MyNode({
tag: 'head',
content: [],
}),
),
)
}
}
}
tree.match({ tag: 'head' }, (node) => {
const cssPreloadHrefs =
cssToPreload[osAgnosticPath(entry, root)] || []
node.content = [
// TODO maybe include imported fonts as links?
...cssPreloadHrefs.map((href) => {
href = path.posix.join(
basePath,
slash(path.relative(outDir, href)),
)
return MyNode({
tag: 'link',
attrs: {
href,
rel: 'preload',
as: 'style',
},
})
}),
...cssToInject.map((href) => {
href = path.posix.join(
basePath,
slash(path.relative(outDir, href)),
)
return MyNode({
tag: 'link',
attrs: {
href,
rel: 'stylesheet',
},
})
}),
...(node.content || []),
]
return node
})
},
// !minify && beautify({ rules: { indent: 2 } }),
].filter(Boolean),
)
const result = await transformer.process(html).catch((e) => {
throw new Error(
`Cannot process html with posthtml: ${e}\n${html}`,
)
})
let htmlOutputDirname = path.normalize(
path.dirname(path.relative(root, entry)),
)
// remove `public` from entry path
if (htmlOutputDirname.startsWith('public')) {
htmlOutputDirname = htmlOutputDirname.replace(
/public(\/|\\)?/,
'',
)
}
const outputHtmlPath = path.resolve(
outDir,
htmlOutputDirname,
path.basename(entry),
)
await fs.ensureDir(path.dirname(outputHtmlPath))
await fs.writeFile(outputHtmlPath, result.html)
// emit html to dist directory, in dirname same as the output files corresponding to html entries
} else {
// if entry is not html, create an html file that imports the js output bundle
}
}
logger.log(`Saved files to ./${osAgnosticPath(outDir, process.cwd())}`)
if (!logger.silent) {
console.info(
printStats({
dependencyStats: metafileToStats({ meta, destLoc: outDir }),
destLoc: path.basename(outDir),
}),
)
}
logger.log(
`Built to ${
/^\w/.test(outDir) ? './' + outDir : outDir
} in ${computeDuration(startTime)}`,
)
return {
bundleMap,
// TODO rebuild should also trigger index.html rewrite, wrap rebuild function
rebuild,
traversalGraph,
}
}
function insertAfterStrings(items, node) {
const [strings, nonStrings] = partition(items, (x) => typeof x === 'string')
return [...strings, node, ...nonStrings]
}
function MyNode(x: Partial<Node>): Node {
return x as any
}
function traverseGraphDown(args: {
traversalGraph: Record<string, string[]>
entryPoints: string[]
onNode
}) {
const { entryPoints, traversalGraph, onNode } = args
const toVisit: string[] = entryPoints
const visited = new Set<string>()
while (toVisit.length) {
const entry = toVisit.shift()
if (!entry || visited.has(entry)) {
break
}
visited.add(entry)
const imports = traversalGraph[entry]
if (!imports) {
throw new Error(
`Node for '${entry}' not found in graph: ${JSON.stringify(
JSON.stringify(Object.keys(traversalGraph), null, 4),
)}`,
)
}
if (onNode) {
onNode(entry)
}
toVisit.push(...imports)
}
}
================================================
FILE: bundless/src/cli.ts
================================================
#!/usr/bin/env node
require('source-map-support').install()
if (process.argv.includes('--debug')) {
process.env.DEBUG_BUNDLESS = 'true'
}
import degit from 'degit'
import prompts from 'prompts'
import deepMerge from 'deepmerge'
import yargs, { CommandModule } from 'yargs'
import { build } from './build'
import { Config, loadConfig } from './config'
import { CONFIG_NAME, EXAMPLES_FOLDERS } from './constants'
import { serve } from './serve'
import { logger } from './logger'
import path from 'path'
const serveCommand: CommandModule = {
command: ['dev', 'serve', '*'],
builder: (argv) => {
argv.option('port', {
alias: 'p',
type: 'number',
description: 'The port for the dev server',
})
argv.option('force', {
alias: 'f',
type: 'boolean',
description:
'Force prebundling even if dependencies did not change',
})
return argv
},
handler: prettyPrintErrors(async (argv: any) => {
const loadedConfig = loadConfig(process.cwd(), argv.config)
const configFromArgv: Config = {
prebundle: { force: argv.force },
server: {
port: argv.port,
},
printStats: argv.stats,
}
let config: Config = deepMerge(loadedConfig, configFromArgv)
return await serve(config)
}),
}
const buildCommand: CommandModule = {
command: ['build'],
builder: (argv) => {
argv.option('outDir', {
alias: 'o',
type: 'string',
description: 'The output directory',
})
return argv
},
handler: prettyPrintErrors(async (argv: any) => {
let config = loadConfig(process.cwd(), argv.config)
const configFromArgv: Config = {
build: {
outDir: argv.outDir,
},
printStats: argv.stats,
}
config = deepMerge(config, configFromArgv)
return await build({
...config,
})
}),
}
const quickstartCommand: CommandModule = {
command: ['quickstart <outDir>'],
builder: (argv) => {
argv.positional('outDir', { type: 'string' })
return argv
},
handler: prettyPrintErrors(async (argv: any) => {
const exampleDir = await prompts({
type: 'select',
name: 'value',
message: 'What example do you want to use?',
choices: EXAMPLES_FOLDERS.map(
(message: string): prompts.Choice => ({
title: message,
value: message,
}),
),
})
if (!exampleDir.value) {
logger.log(`Nothing done`)
return
}
logger.log(`Downloading ${exampleDir.value} example to ${argv.outDir}`)
const emitter = degit(
path.posix.join('remorses/bundless/examples', exampleDir.value),
{
verbose: true,
},
)
emitter.on('info', (info) => {
logger.debug(info.message)
})
await emitter.clone(argv.outDir)
logger.log(`Downloaded example to ./${path.normalize(argv.outDir)}`)
}),
}
yargs
.scriptName('bundless')
.locale('en')
.option('config', {
alias: 'c',
type: 'string',
default: CONFIG_NAME,
description: `The config path to use`,
})
.option('debug', {
type: 'boolean',
description: `Enables debug logging`,
})
.option('stats', {
type: 'boolean',
description: 'Show profiling stats',
})
.command(serveCommand)
.command(buildCommand)
.command(quickstartCommand)
.version()
.help('help', 'h').argv
function prettyPrintErrors(fn) {
return async (...args) => {
try {
return await fn(...args)
} catch (e) {
logger.error(e.message)
logger.error(e.stack)
}
}
}
================================================
FILE: bundless/src/client/template.ts
================================================
// This file runs in the browser.
// injected by serverPluginClient when served
declare const sourceMapSupport: any
declare const __HMR_PROTOCOL__: string
declare const __HMR_HOSTNAME__: string
declare const __HMR_PORT__: string
declare const __HMR_TIMEOUT__: number
declare const __HMR_ENABLE_OVERLAY__: boolean
declare const __DEFINES__: Record<string, any>
const defines = __DEFINES__
Object.keys(defines).forEach((key) => {
const segs = key.split('.')
let target = window as any
for (let i = 0; i < segs.length; i++) {
const seg = segs[i]
if (i === segs.length - 1) {
target[seg] = defines[key]
} else {
target = target[seg] || (target[seg] = {})
}
}
})
import {
OverlayErrorPayload,
HMRPayload,
UpdatePayload,
OverlayInfoOpenPayload,
} from './types'
// use server configuration, then fallback to inference
const socketProtocol =
__HMR_PROTOCOL__ || (location.protocol === 'https:' ? 'wss' : 'ws')
const socketHost = `${__HMR_HOSTNAME__ || location.hostname}:${__HMR_PORT__}`
const socketURL = `${socketProtocol}://${socketHost}`
const isWindowDefined = typeof window !== 'undefined'
function log(...args) {
console.info('[ESM-HMR]', ...args)
}
function reload() {
if (!isWindowDefined) {
return
}
location.reload(true)
}
let SOCKET_MESSAGE_QUEUE: HMRPayload[] = []
let connected = false
function _sendSocketMessage(msg) {
socket.send(JSON.stringify(msg))
}
function sendSocketMessage(msg: HMRPayload) {
if (!connected) {
SOCKET_MESSAGE_QUEUE.push(msg)
} else {
_sendSocketMessage(msg)
}
}
const socket = new WebSocket(socketURL, 'esm-hmr')
const REGISTERED_MODULES: { [path: string]: HotModuleState } = {}
class HotModuleState {
data = {}
isLocked = false
isDeclined = false
isAccepted = false
acceptCallbacks: { deps: string[]; callback: Function }[] = []
disposeCallbacks: Function[] = []
path = ''
constructor(path) {
this.path = path
}
lock() {
this.isLocked = true
}
dispose(callback) {
this.disposeCallbacks.push(callback)
}
invalidate() {
reload()
}
decline() {
this.isDeclined = true
}
accept(_deps, callback: Function | true = true) {
if (this.isLocked) {
return
}
if (!this.isAccepted) {
sendSocketMessage({ path: this.path, type: 'hotAccept' })
this.isAccepted = true
}
if (!Array.isArray(_deps)) {
callback = _deps || callback
_deps = []
}
if (callback === true) {
callback = () => {}
}
const deps = _deps.map((dep) => {
return new URL(dep, `${window.location.origin}${this.path}`)
.pathname
})
this.acceptCallbacks.push({
deps,
callback,
})
}
}
export function createHotContext(fullUrl) {
const id = new URL(fullUrl).pathname
const existing = REGISTERED_MODULES[id]
if (existing) {
existing.lock()
runModuleDispose(id)
return existing
}
const state = new HotModuleState(id)
REGISTERED_MODULES[id] = state
return state
}
/** Called when a new module is loaded, to pass the updated module to the "active" module */
// uses the graph lastUsedTimestamp to make the new timestamp to fetch, pass this in the hmr message?
async function runModuleAccept({ path, namespace, updateID }: UpdatePayload) {
const state = REGISTERED_MODULES[path]
if (!state) {
log(`${path} has not been registered, reloading`)
log(Object.keys(REGISTERED_MODULES))
return false
}
if (state.isDeclined) {
log(`${path} has declined HMR, reloading`)
return false
}
const acceptCallbacks = state.acceptCallbacks
for (const { deps, callback: acceptCallback } of acceptCallbacks) {
const encodedNamespace = encodeURIComponent(namespace || 'file')
const [module, ...depModules] = await Promise.all([
import(
appendQuery(path, `namespace=${encodedNamespace}&t=${updateID}`)
),
...deps.map(
(d) => import(appendQuery(d, `t=${Date.now()}&namespace=file`)),
), // TODO deps should have the namespace and their update ids too, how?
])
acceptCallback({ module, deps: depModules })
}
return true
}
/** Called when a new module is loaded, to run cleanup on the old module (if needed) */
async function runModuleDispose(id) {
const state = REGISTERED_MODULES[id]
if (!state) {
return false
}
if (state.isDeclined) {
return false
}
const disposeCallbacks = state.disposeCallbacks
state.disposeCallbacks = []
state.data = {}
disposeCallbacks.map((callback) => callback())
return true
}
function getErrorMessageMappedSource(message) {
if (typeof sourceMapSupport !== 'undefined') {
return (
sourceMapSupport.getErrorSource({
message,
name: '',
stack: '',
}) || message
)
}
return message
}
function getErrorStackMappedSource(stack) {
if (typeof sourceMapSupport !== 'undefined') {
return (
sourceMapSupport
.getErrorSource({
stack,
message: '',
name: '',
})
?.trim?.() || stack
)
}
return stack
}
socket.addEventListener('message', ({ data: _data }) => {
if (!_data) {
return
}
const data: HMRPayload = JSON.parse(_data)
if (data.type === 'connected') {
connected = true
SOCKET_MESSAGE_QUEUE.forEach(_sendSocketMessage)
SOCKET_MESSAGE_QUEUE = []
setInterval(() => {
try {
socket.send(JSON.stringify({ type: 'ping' }))
} catch {}
}, __HMR_TIMEOUT__)
return
}
if (data.type === 'reload') {
log('message: reload')
reload()
return
}
if (data.type === 'overlay-error') {
log('message: error')
InfoOverlay.clear()
ErrorOverlay.show(data.err)
return
}
if (data.type === 'overlay-info-open') {
log('message: info open')
ErrorOverlay.clear()
InfoOverlay.show({ ...data.info, stack: '' })
return
}
if (data.type === 'overlay-info-close') {
log('message: info close')
InfoOverlay.clear()
return
}
if (data.type === 'update') {
if (ErrorOverlay.isOpen()) {
log(`error overlay is open: reloading`)
return reload()
}
log('message: update', data)
runModuleAccept(data)
.then((ok) => {
if (ok) {
ErrorOverlay.clear()
InfoOverlay.clear()
} else {
reload()
}
})
.catch((err) => {
console.error('[ESM-HMR] Hot Update Error', err)
// A failed import gives a TypeError, but invalid ESM imports/exports give a SyntaxError.
// Failed build results already get reported via a better WebSocket update.
// We only want to report invalid code like a bad import that doesn't exist.
if (err instanceof SyntaxError) {
ErrorOverlay.show({
message: `Hot Update Error for ${data.path}: ${err.message}`,
stack: err.stack || '',
})
}
})
return
}
log('message: unknown', data)
})
log('listening for file changes...')
/** Runtime error reporting: If a runtime error occurs, show it in an overlay. */
if (isWindowDefined) {
window.addEventListener('error', function (event) {
const err: OverlayErrorPayload['err'] = {
message: `${event.message}`,
stack: event.error ? event.error.stack : '',
}
ErrorOverlay.show(err)
})
}
const enableOverlay = __HMR_ENABLE_OVERLAY__
function appendQuery(url: string, query: string) {
if (query.startsWith('?')) {
query = query.slice(1)
}
if (url.includes('?')) {
return url + query
}
return `${url}?${query}`
}
const template = ({ mainColor, tip = '' }) => /*html*/ `
<style>
:host {
position: fixed;
z-index: 1000001;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow-y: scroll;
margin: 0;
background: rgba(0, 0, 0, 0.66);
--monospace: 'SFMono-Regular', Consolas,
'Liberation Mono', Menlo, Courier, monospace;
--red: #ff5555;
--yellow: #e2aa53;
--purple: #cfa4ff;
--cyan: #2dd9da;
--dim: #c9c9c9;
}
.window {
font-family: var(--monospace);
line-height: 1.5;
width: 800px;
color: #d8d8d8;
margin: 30px auto;
padding: 25px 40px;
position: relative;
background: #000;
border-radius: 6px 6px 8px 8px;
box-shadow: 0 19px 38px rgba(0,0,20,0.01), 0 15px 12px rgba(0,0,20,0.1);
overflow: hidden;
border-top: 8px solid var(${mainColor});
min-height: 200px;
}
pre {
font-family: var(--monospace);
font-size: 16px;
margin-top: 0;
margin-bottom: 1em;
overflow-x: scroll;
scrollbar-width: none;
}
pre::-webkit-scrollbar {
display: none;
}
.message {
line-height: 1.3;
font-weight: 600;
white-space: pre-wrap;
}
.message-body {
color: var(${mainColor});
}
.plugin {
color: var(--purple);
}
.file {
color: var(--cyan);
margin-bottom: 0;
white-space: pre-wrap;
word-break: break-all;
}
.frame {
color: var(--yellow);
}
.stack {
font-size: 13px;
color: var(--dim);
}
.tip {
font-size: 13px;
color: #999;
border-top: 1px dotted #999;
padding-top: 13px;
}
code {
font-size: 13px;
font-family: var(--monospace);
color: var(--yellow);
}
.file-link {
text-decoration: underline;
cursor: pointer;
}
</style>
<div class="window">
<pre class="message">
<span class="plugin"></span><span class="message-body"></span>
</pre>
<pre class="file"></pre>
<pre class="frame"></pre>
<pre class="stack"></pre>
${
tip &&
`<div class="tip">
${tip}
</div>
`
}
</div>
`
class CommonOverlay extends HTMLElement {
root?: ShadowRoot
static overlayId: string = 'overlay'
static isOpen() {
const elements = document.querySelectorAll(this.overlayId)
return elements.length > 0
}
static show(arg) {
if (!enableOverlay) return
this.clear()
// @ts-ignore
const instance = new this(arg)
document.body.appendChild(instance)
}
static clear() {
document
.querySelectorAll(this.overlayId)
.forEach((n) => (n as ErrorOverlay).close())
}
close() {
this.parentNode?.removeChild(this)
}
displayText(selector: string, text: string, linkFiles = false) {
const el = this.root!.querySelector(selector)!
if (!linkFiles) {
el.textContent = text
} else {
// TODO also match normal file paths
const matches = getAllMatches(text, /(https?:\/\/.*)/g)
for (let { frag, matched } of matches) {
el.appendChild(document.createTextNode(frag))
const link = document.createElement('a')
link.textContent = matched
link.className = 'file-link'
const isUrl = /https?:\/\//.test(matched)
let path = isUrl ? new URL(matched).pathname : matched
const fileLocationRegex = /(:\d+:\d+)$/
if (!fileLocationRegex.test(path)) {
const lineNumAndCol =
fileLocationRegex.exec(matched)?.[1] || ''
path += lineNumAndCol
}
link.onclick = () => {
console.info(`Opening ${path} in editor`)
fetch('/__open-in-editor?file=' + encodeURIComponent(path))
}
el.appendChild(link)
}
}
}
}
function getAllMatches(text: string, regex: RegExp) {
let curIndex = 0
let match
const matches: { frag: string; matched: string }[] = []
while ((match = regex.exec(text))) {
let { 0: matched, index } = match
matched = matched.trim()
if (index != null) {
const frag = text.slice(curIndex, index)
matches.push({ frag, matched })
curIndex += frag.length + matched.length
}
}
return matches
}
export class ErrorOverlay extends CommonOverlay {
root: ShadowRoot
static overlayId = 'bundless-error-overlay'
constructor(err: OverlayErrorPayload['err']) {
console.log({ err })
super()
this.root = this.attachShadow({ mode: 'open' })
this.root.innerHTML = template({
mainColor: '--red',
tip: `Click outside or fix the code to dismiss.<br>`,
})
if (err.plugin) {
this.displayText('.plugin', `[plugin:${err.plugin}] `)
}
const message = getErrorMessageMappedSource(err.message)
this.displayText('.message-body', message.trim())
const stack = getErrorStackMappedSource(err.stack)
this.displayText('.stack', stack.trim(), true)
this.root.querySelector('.window')!.addEventListener('click', (e) => {
e.stopPropagation()
})
this.addEventListener('click', () => {
this.close()
})
}
}
customElements.define(ErrorOverlay.overlayId, ErrorOverlay)
export class InfoOverlay extends CommonOverlay {
root: ShadowRoot
static overlayId = 'bundless-info-overlay'
constructor(info: OverlayInfoOpenPayload['info']) {
super()
this.root = this.attachShadow({ mode: 'open' })
this.root.innerHTML = template({ mainColor: '--cyan' })
this.displayText('.message-body', info.message.trim())
this.root.querySelector('.window')!.addEventListener('click', (e) => {
e.stopPropagation()
})
// this.addEventListener('click', () => {
// this.close()
// })
}
}
customElements.define(InfoOverlay.overlayId, InfoOverlay)
// InfoOverlay.show({ message: 'Prebundling modules' })
================================================
FILE: bundless/src/client/types.ts
================================================
export type HMRPayload =
| ConnectedPayload
| UpdatePayload
| FullReloadPayload
| OverlayErrorPayload
| OverlayInfoOpenPayload
| OverlayInfoClosePayload
| HotAcceptPayload
| ConnectPayload
interface ConnectedPayload {
type: 'connected'
}
export interface UpdatePayload {
type: 'update'
path: string
updateID: string
namespace: string
// changeSrcPath: string
// timestamp: number
}
interface FullReloadPayload {
type: 'reload'
}
interface HotAcceptPayload {
type: 'hotAccept'
path: string
}
interface ConnectPayload {
type: 'connected'
}
export interface OverlayErrorPayload {
type: 'overlay-error'
err: {
// [name: string]: any
message: string
stack: string
id?: string
frame?: string
plugin?: string
pluginCode?: string
}
}
export interface OverlayInfoOpenPayload {
type: 'overlay-info-open'
info: {
[name: string]: any
message: string
showSpinner?: boolean
}
}
export interface OverlayInfoClosePayload {
type: 'overlay-info-close'
}
================================================
FILE: bundless/src/config.ts
================================================
import { CONFIG_NAME, DEFAULT_PORT } from './constants'
import findUp from 'find-up'
import fs from 'fs'
import { Plugin, PluginsExecutor } from './plugins-executor'
import path from 'path'
import * as esbuild from 'esbuild'
import deepmerge from 'deepmerge'
export async function getEntries(
pluginsExecutor: PluginsExecutor,
config: Config,
) {
const root = pluginsExecutor.ctx.root
if (config.entries) {
// for (let entry of config.entries) {
// if (config.platform === 'browser' && !entry.endsWith('.html')) {
// throw new Error(
// `When targeting browser config.entries can only contain html files: ${entry}`,
// )
// }
// }
return (
await Promise.all(
config.entries.map((x) =>
pluginsExecutor
.resolve({
path: x,
resolveDir: config.root,
skipOnResolved: true,
})
.then((x) => x?.path || ''),
),
)
)
.filter(Boolean)
.map((x) => path.resolve(root, x))
}
// public folder logic is already in the html resolver plugin
const index1 = await pluginsExecutor.resolve({
path: 'index.html',
skipOnResolved: true,
resolveDir: config.root,
})
if (index1?.path) {
return [path.resolve(root, index1.path)]
}
throw new Error(
`Cannot find entries, neither config.entries, index.html or public/index.html files are present\n${JSON.stringify(
config,
null,
4,
)}`,
)
}
export type Platform = 'node' | 'browser'
export function normalizeConfig(config: Config) {
config = deepmerge(defaultConfig, config)
config.plugins = (config.plugins || [])
.filter(Boolean)
.map((x) => ({ ...x, enforce: x.enforce || 'pre' }))
return config
}
// TODO add config.mainFields
// TODO add config.build.chunkNames, assetNames, entryNames
// TODO add config.inject
// TODO add config.watch
// TODO add config.resolveExtensions
// TODO add config.jsxFactory, jsxFragment
export interface Config {
server?: ServerConfig
define?: Record<string, string>
prebundle?: PrebundlingConfig
build?: BuildConfig
printStats?: boolean
platform?: Platform
root?: string
// env?: Record<string, string>
entries?: string[]
plugins?: Plugin[]
// TODO rename to loader to stay closer to esbuild
loader?: Record<string, esbuild.Loader> // TODO support more than file
jsx?:
| 'vue'
| 'preact'
| 'react'
| {
factory?: string
fragment?: string
}
}
export interface PrebundlingConfig {
force?: boolean
includeWorkspacePackages?: string[] | boolean // TODO if bundless is called on root this won't work (example is vitro), every path won't ever be outside root
}
export interface ServerConfig {
openBrowser?: boolean
cors?: boolean
port?: number | string
hmr?: HmrConfig | boolean
}
export const defaultConfig: Config = {
// entries: ['index.html'], // entry files
server: {
port: 3000,
hmr: true,
openBrowser: false, // opens browser on server start
},
prebundle: {
includeWorkspacePackages: false, // linked packages to prebundle
force: false, // forces prebundling dependencies on server start
},
build: {
basePath: '/',
jsTarget: 'es2018', // target es version
minify: true, // run esbuild minification
outDir: './out', // output directory
},
platform: 'browser', // target platform, browser or node
loader: {}, // extension that return their path when imported
jsx: 'react', // jsx preset
plugins: [],
define: {},
}
export function loadConfig(from: string, name = CONFIG_NAME): Config {
const configPath = findUp.sync(name, { cwd: from })
let config: Config = {}
if (configPath) {
config = require(configPath)
}
if (!config.root) {
config = { ...config, root: process.cwd() }
}
return config
}
export interface HmrConfig {
protocol?: string
hostname?: string
port?: number
path?: string
/**
* If you are using hmr ws proxy, it maybe timeout with your proxy program.
* You can set this option to let client send ping socket to keep connection alive.
* The option use `millisecond` as unit.
* @default 30000ms
*/
timeout?: number
}
export interface BuildConfig {
basePath?: string
outDir?: string
minify?: boolean
jsTarget?: string
}
================================================
FILE: bundless/src/constants.ts
================================================
import { logger } from './logger'
import * as esbuild from 'esbuild'
export let isRunningWithYarnPnp: boolean = false
export let pnpapi: any
try {
pnpapi = require('pnpapi')
isRunningWithYarnPnp = Boolean(pnpapi)
logger.debug('Using Yarn PnP')
} catch {}
export const hmrClientNamespace = 'hmr-client'
export const DEFAULT_PORT = 3000
export const CLIENT_PUBLIC_PATH = `/_hmr_client.js?namespace=${hmrClientNamespace}`
export const COMMONJS_ANALYSIS_PATH = '.bundless/commonjs.json'
export const WEB_MODULES_PATH = '.bundless/node_modules'
export const BUNDLE_MAP_PATH = '.bundless/bundleMap.json'
export const HMR_SERVER_NAME = 'esm-hmr'
export const CONFIG_NAME = 'bundless.config.js'
export const EXAMPLES_FOLDERS = [
'react-typescript',
'react-javascript',
'vanilla-javascript',
'svelte',
]
export const MAIN_FIELDS = ['browser:module', 'browser', 'module', 'main']
export const showGraph = process.env.SHOW_HMR_GRAPH
export const JS_EXTENSIONS = ['.ts', '.tsx', '.mjs', '.js', '.jsx', '.cjs']
export const defaultLoader: Record<string, esbuild.Loader> = {
'.jpg': 'file',
'.jpeg': 'file',
'.png': 'file',
'.svg': 'dataurl',
'.gif': 'file',
'.ico': 'file',
'.webp': 'file',
'.jp2': 'file',
'.avif': 'file',
'.woff': 'file',
'.woff2': 'file',
'.ttf': 'file',
}
export const defaultImportableAssets = Object.keys(defaultLoader)
export const hmrPreamble = `import * as __HMR__ from '${CLIENT_PUBLIC_PATH}'; import.meta.hot = __HMR__.createHotContext(import.meta.url); `
================================================
FILE: bundless/src/hmr-graph.ts
================================================
import path from 'path'
import WebSocket from 'ws'
import net from 'net'
import crypto from 'crypto'
import chalk from 'chalk'
import { osAgnosticPath } from './utils'
import { fileToImportPath, importPathToFile } from './utils'
import { HMRPayload } from './client/types'
import { logger } from './logger'
import { HMR_SERVER_NAME } from './constants'
import slash from 'slash'
import { PluginsExecutor } from './plugins-executor'
// examples are ./main.js and ../folder/main.js
type OsAgnosticPath = string
// examples are /path/file.js or /__..__/file.js
type ImportPath = string
export interface HmrNode {
hash: string
importers(): Set<OsAgnosticPath> // returns osAgnosticPaths
importees: Set<ImportPath>
dirtyImportersCount: number // modules that have imported this and have been updated
lastUsedTimestamp: number
isHmrEnabled?: boolean
hasHmrAccept?: boolean
computedModules?: Set<OsAgnosticPath>
}
export class HmrGraph {
// keys are always os agnostic paths and not public paths
nodes: { [osAgnosticPath: string]: HmrNode } = {}
root
wss: WebSocket.Server
server: net.Server
realToFake: Record<string, Set<string>>
constructor({ root, server }: { root: string; server: net.Server }) {
this.realToFake = {}
this.nodes = {}
this.root = root
this.server = server
const wss = new WebSocket.Server({ noServer: true })
this.wss = wss
server.once('close', () => {
wss.close(() => logger.debug('closing wss'))
wss.clients.forEach((client) => {
client.close()
})
})
server.on('upgrade', (req, socket, head) => {
if (req.headers['sec-websocket-protocol'] === HMR_SERVER_NAME) {
wss.handleUpgrade(req, socket, head, (ws) => {
wss.emit('connection', ws, req)
})
}
})
wss.on('connection', (socket) => {
socket.send(JSON.stringify({ type: 'connected' }))
socket.on('message', (data) => {
const message: HMRPayload = JSON.parse(data.toString())
if (message.type === 'hotAccept') {
this.ensureEntry(importPathToFile(root, message.path), {
hasHmrAccept: true,
isHmrEnabled: true,
})
}
})
})
wss.on('error', (e: Error & { code: string }) => {
if (e.code !== 'EADDRINUSE') {
console.error(chalk.red(`WebSocket server error:`))
console.error(e)
}
})
}
sendHmrMessage(payload: HMRPayload) {
if (!this.wss) {
throw new Error(`HMR Websocket server has not started yet`)
}
const stringified = JSON.stringify(payload, null, 4)
logger.debug(`hmr: ${stringified}`)
if (!this.wss.clients.size) {
logger.debug(`No clients listening for HMR message`)
}
let clientIndex = 1
for (let client of this.wss.clients.values()) {
if (client.readyState === WebSocket.OPEN) {
client.send(stringified)
} else {
logger.log(
chalk.red(
`Cannot send HMR message, hmr client ${clientIndex} is not open`,
),
)
}
clientIndex += 1
}
}
ensureEntry(path: string, newNode?: Partial<HmrNode>): HmrNode {
path = osAgnosticPath(path, this.root)
if (this.nodes[path]) {
Object.assign(this.nodes[path], newNode || {})
return this.nodes[path]
}
this.nodes[path] = {
hash:
process.env.BUNDLESS_CONSISTENT_HMR_GRAPH_HASH != null
? process.env.BUNDLESS_CONSISTENT_HMR_GRAPH_HASH
: crypto.randomBytes(4).toString('hex'),
dirtyImportersCount: 0,
lastUsedTimestamp: 0,
hasHmrAccept: false,
isHmrEnabled: false,
importees: new Set(),
...newNode,
importers: () => {
const importPath = fileToImportPath(this.root, path)
return new Set(
Object.entries(this.nodes)
.filter(([_, v]) => {
return v.importees?.has(importPath)
})
.map(([k, _]) => k),
)
},
}
return this.nodes[path]
}
toString() {
const content = Object.keys(this.nodes)
.map((k) => {
const node = this.nodes[k]
let key = slash(path.relative(process.cwd(), k))
if (node.hasHmrAccept) {
key = chalk.redBright(chalk.underline(key))
} else if (node.isHmrEnabled) {
key = chalk.yellow(chalk.underline(key))
}
key += ' ' + chalk.cyan(node.dirtyImportersCount)
return ` ${key} -> ${JSON.stringify(
[...node.importees],
null,
4,
)
.split('\n')
.map((x) => ' ' + x)
.join('\n')
.trim()}`
})
.join('\n')
const legend =
`\nLegend:\n` +
// `${'[ ]'} has no HMR\n` +
`${chalk.redBright('[ ]')} accepts HMR\n` +
`${chalk.yellow('[ ]')} HMR enabled\n\n`
return legend + `ImportGraph {\n${content}\n}\n`
}
// TODO maybe rewrite should happen before to prune the graph from removed imports? in case old imports remain in the graph what could happen? the hmr algo only depend on the importers, this means that the worst thing could be that a non importer could be updated, but this is impossible because the only changed imports can only be the ones in the updated file, this means that only the current file imports could be invalid, which means that changed files importers will always be valid
// TODO to make this work for vue and vite, i need to support virtual files?, vite files will be rewritten as js files with imports of virtual css files, the current implementation will see the change in the vite file, but it cannot know about changed virtual files, maybe i can put a property in the result of onTransform or onLoad to say `computedFiles: [virtualFile]`, save this info in graph (taken during rewrite) and in onChange i can send an update to these dependent modules too
// TODO batch changes? this way if user dopy pastes a directory i don't have to traverse the graph for every file
async onFileChange({ filePath }: { filePath: string }) {
const graph = this
const root = this.root
const resolvedPaths = this.realToFake[filePath]
? [...this.realToFake[filePath]]
: [filePath]
const initialRelativePaths = resolvedPaths.map((resolvedPath) =>
osAgnosticPath(resolvedPath, root),
)
const messages: HMRPayload[] = []
const nodesToBeFetched: Set<string> = new Set([])
// update all importers query fetch to not use browser cached module
await this.traverseUpwards({
entries: initialRelativePaths,
onTraverse: async (
relativePath: string,
node: HmrNode,
importers: Set<string>,
) => {
// can be a non js file, like index.html
if (!node) {
return true
}
nodesToBeFetched.add(relativePath)
if (node.computedModules) {
for (let computed of node.computedModules) {
const node = graph.nodes[computed]
node.lastUsedTimestamp++
}
}
for (let importer of importers) {
nodesToBeFetched.add(importer)
}
return true
},
})
for (const relativePath of nodesToBeFetched) {
const node = graph.ensureEntry(relativePath)
node.lastUsedTimestamp++
}
await this.traverseUpwards({
entries: initialRelativePaths,
onTraverse: async (
relativePath: string,
node: HmrNode,
importers: Set<string>,
) => {
const importPath = fileToImportPath(root, relativePath)
// can be a non js file, like index.html
if (!node) {
logger.log(
`node for '${relativePath}' not found in graph, reloading`,
)
this.sendHmrMessage({ type: 'reload' })
return
}
// trigger an update if the module is able to handle it
if (node.isHmrEnabled) {
messages.push({
type: 'update',
namespace: 'file',
path: importPath,
updateID: node.hash + node.lastUsedTimestamp,
})
// computed nodes are virtual nodes whose code depends on another node
if (node.computedModules) {
for (let computed of node.computedModules) {
const node = graph.nodes[computed]
node.dirtyImportersCount++
messages.push({
type: 'update',
namespace: 'file', // TODO do not hard code namespace for computed nodes
path: fileToImportPath(root, computed),
updateID: node.hash + node.lastUsedTimestamp,
})
}
}
}
// reached a boundary, stop hmr propagation
if (node.hasHmrAccept) {
return
}
// reached another boundary, reload
if (!importers.size) {
logger.log(
`reached top boundary '${relativePath}', reloading`,
)
this.sendHmrMessage({ type: 'reload' })
return
}
for (let importer of importers) {
graph.ensureEntry(importer)
// mark module as dirty, importers will refetch this module to see updates
node.dirtyImportersCount++ // TODO this means that the current node t must be changed, not the importer one, but given that now i include the t on every one maybe no nee d for this?
}
return true
},
})
messages.forEach((m) => this.sendHmrMessage(m))
}
async traverseUpwards({ onTraverse, entries }) {
const graph = this
const toVisit: string[] = [...entries]
const visited: string[] = []
while (toVisit.length) {
const relativePath = toVisit.shift()
if (!relativePath || visited.includes(relativePath)) {
continue
}
visited.push(relativePath)
// TODO if plugin resolver like css changes filename, it won't be found in graph
const node = graph.nodes[relativePath]
if (!node) {
return
}
const importers = node.importers()
const res = await onTraverse(relativePath, node, importers)
if (res) {
toVisit.push(...importers)
}
}
}
}
================================================
FILE: bundless/src/index.ts
================================================
export { serve } from './serve'
export { build } from './build'
export { Config, loadConfig } from './config'
export { Plugin, PluginsExecutor } from './plugins-executor'
export { logger, Logger } from './logger'
export { HmrGraph, HmrNode } from './hmr-graph'
export { MAIN_FIELDS } from './constants'
================================================
FILE: bundless/src/logger.ts
================================================
import chalk from 'chalk'
import ora, { Ora } from 'ora'
const defaultPrefix = '[bundless] '
const DEBUG = process.env.DEBUG_BUNDLESS
export class Logger {
prefix: string = ''
silent: boolean
constructor({ prefix = defaultPrefix, silent = false } = {}) {
this.prefix = prefix
this.silent = silent
}
private print(x) {
if (this.silent) {
return
}
if (this.spinner) {
this.spinner.info(x)
} else {
process.stderr.write(chalk.dim(this.prefix) + x + '\n')
}
}
log(...x) {
this.print(x.join(' '))
}
warn(...x) {
this.print(chalk.yellow(x.join(' ')))
}
error(...x) {
this.print(chalk.red(x.join(' ')))
}
private spinner?: Ora
spinStart(text: string) {
if (this.silent) {
return
}
this.spinner = ora(text + '\n\n').start()
}
spinSucceed(text: string) {
if (this.spinner) {
this.spinner.succeed(text)
}
this.spinner = undefined
}
spinFail(text: string) {
if (this.spinner) {
this.spinner.fail(chalk.redBright(text))
}
this.spinner = undefined
}
debug = DEBUG
? (...x) => {
if (this.spinner) {
this.spinner.info(x.join(' ') + '\n')
} else {
process.stderr.write(
chalk.dim(this.prefix + x.join(' ') + '\n'),
)
}
}
: () => {}
}
export const logger = new Logger()
================================================
FILE: bundless/src/middleware/history-fallback.ts
================================================
import { Middleware } from 'koa'
import path from 'path'
import slash from 'slash'
import { logger } from '../logger'
import { PluginsExecutor } from '../plugins-executor'
import { cleanUrl, importPathToFile } from '../utils'
export function historyFallbackMiddleware({
root,
pluginsExecutor,
}: {
root: string
pluginsExecutor: PluginsExecutor
}): Middleware {
return async (ctx, next) => {
if (ctx.status !== 404) {
return next()
}
if (ctx.method !== 'GET') {
logger.debug(`not redirecting ${ctx.url} (not GET)`)
return next()
}
const accept = ctx.headers && ctx.headers.accept
if (typeof accept !== 'string') {
logger.debug(`not redirecting ${ctx.url} (no headers.accept)`)
return next()
}
if (accept.includes('application/json')) {
logger.debug(`not redirecting ${ctx.url} (json)`)
return next()
}
if (!accept.includes('text/html')) {
logger.debug(`not redirecting ${ctx.url} (not accepting html)`)
return next()
}
// use the executor to resolve virtual html files
// TODO decide if we want to pass to plugins the path with appended index.html or the normal path and let the plugins decide if they watn to serve html, the second way is harder because html should be served as last thing (fallback) but user plugins run first
let filePath = !cleanUrl(ctx.path).endsWith('.html')
? path.posix.join(ctx.path, 'index.html')
: ctx.path
const {
contents: resolvedHtml,
path: resolveHtmlPath,
} = await pluginsExecutor.resolveLoadTransform({
path: importPathToFile(root, filePath),
skipOnResolved: true,
expectedExtensions: ['.html'],
})
if (resolvedHtml) {
send(
ctx,
resolvedHtml,
'/' + slash(path.relative(root, resolveHtmlPath || '')),
)
return next()
}
logger.debug(`fallback ${ctx.url} to html`)
// html resolver already search in public
const {
contents: resolvedTopHtml,
} = await pluginsExecutor.resolveLoadTransform({
path: path.resolve(root, 'index.html'),
skipOnResolved: true,
expectedExtensions: ['.html'],
})
if (resolvedTopHtml) {
send(ctx, resolvedTopHtml, '/index.html')
return next()
}
return next()
// return next()
}
}
function send(ctx, resolvedHtml, as = '') {
logger.debug(`Resolved html for ${ctx.path} as ${as}`)
ctx.body = resolvedHtml
ctx.status = 200
ctx.type = 'html'
// return next()
}
================================================
FILE: bundless/src/middleware/index.ts
================================================
export { sourcemapMiddleware } from './sourcemap'
export { historyFallbackMiddleware } from './history-fallback'
export { staticServeMiddleware } from './static-serve'
export { openInEditorMiddleware } from './open-in-editor'
export { pluginsMiddleware } from './plugins'
================================================
FILE: bundless/src/middleware/open-in-editor.ts
================================================
import { logger } from '..'
import fs from 'fs'
import launchEditor from 'launch-editor'
import { importPathToFile } from '../utils'
import { Middleware } from 'koa'
const fileLocationRegex = /(:\d+:\d+)$/
export function openInEditorMiddleware({ root }): Middleware {
return function(ctx, next) {
if (ctx.path !== '/__open-in-editor') {
return next()
}
const { file = '' } = ctx.query || {}
if (!file) {
ctx.res.statusCode = 500
ctx.body = `launch-editor-middleware: required query param "file" is missing.`
return
}
let realPath = fs.existsSync(file.replace(fileLocationRegex, '')) ? file : importPathToFile(root, file)
logger.log(`Opening editor at ${realPath}`)
launchEditor(realPath)
ctx.res.statusCode = 200
ctx.body = `Opened ${realPath}`
}
}
================================================
FILE: bundless/src/middleware/plugins.ts
================================================
import { FSWatcher } from 'chokidar'
import { Middleware } from 'koa'
import { WEB_MODULES_PATH } from '../constants'
import { logger } from '../logger'
import { PluginsExecutor } from '../plugins-executor'
import { importPathToFile, dotdotEncoding, genSourceMapString } from '../utils'
export function pluginsMiddleware({
root,
watcher,
pluginsExecutor,
}: {
root: string
watcher: FSWatcher
pluginsExecutor: PluginsExecutor
}): Middleware {
return async function pluginsMiddleware(ctx, next) {
if (
ctx.query.namespace == null ||
ctx.req.headers['accept'] !== '*/*'
) {
return next()
}
if (ctx.path.startsWith('.')) {
throw new Error(
`All import paths should have been rewritten to absolute paths (start with /)\n` +
` make sure import paths for '${ctx.path}' are statically analyzable`,
)
}
const isVirtual = ctx.query.namespace && ctx.query.namespace !== 'file'
// do not resolve virtual files like node builtins to an absolute path
const resolvedPath = isVirtual
? ctx.path.slice(1) // remove leading /
: importPathToFile(root, ctx.path)
// watch files outside root
if (
ctx.path.startsWith('/' + dotdotEncoding) &&
!resolvedPath.includes('node_modules')
) {
watcher.add(resolvedPath)
}
const namespace = ctx.query.namespace || 'file'
const loaded = await pluginsExecutor.load({
path: resolvedPath,
pluginData: undefined,
namespace,
})
if (loaded?.pluginData) {
logger.warn(
`esbuild pluginData is not supported by bundless, used by plugin ${loaded.pluginName}`,
)
}
if (loaded == null || loaded.contents == null) {
return next()
}
const transformed = await pluginsExecutor.transform({
path: resolvedPath,
loader: loaded.loader || 'default',
namespace,
contents: String(loaded.contents),
})
if (transformed == null) {
return next()
}
const sourcemap = transformed.map
? genSourceMapString(transformed.map)
: ''
ctx.body = transformed.contents + sourcemap
ctx.status = 200
ctx.type = 'js'
const isDep = ctx.path.includes(WEB_MODULES_PATH)
const isCacheableModule = ctx.query.t != null
ctx.set(
'Cache-Control',
isDep || isCacheableModule
? 'max-age=31536000,immutable'
: 'no-cache',
)
return next()
}
}
================================================
FILE: bundless/src/middleware/sourcemap.ts
================================================
import chalk from 'chalk'
import { Middleware } from 'koa'
import path from 'path'
import { RawSourceMap } from 'source-map'
import { logger } from '../logger'
import { importPathToFile, readFile } from '../utils'
// changes sourcemaps to point to right files
export const sourcemapMiddleware = ({ root }): Middleware => {
return async function sourcemap(ctx, next) {
if (!ctx.path.endsWith('.map')) {
return next()
}
logger.debug(`Handling sourcemap request for '${ctx.path}'`)
const filename = importPathToFile(root, ctx.path)
const content = await readFile(filename)
const map: RawSourceMap = JSON.parse(content)
if (!map.sources) {
logger.warn(`No sources found for sourcemap '${ctx.path}'`)
return next()
}
if (map.sourcesContent && map.sources.every(path.isAbsolute)) {
return next()
}
const sourcesContent = map.sourcesContent || []
const sourceRoot = path.resolve(
path.dirname(filename),
map.sourceRoot || '',
)
map.sources = await Promise.all(
map.sources.map(async (source, i) => {
const originalPath = path.resolve(sourceRoot, source)
if (!sourcesContent[i]) {
try {
sourcesContent[i] = await readFile(originalPath)
} catch (err) {
if (err.code === 'ENOENT') {
console.error(
chalk.red(
`Sourcemap "${filename}" points to non-existent source: "${originalPath}"`,
),
)
return source
}
throw err
}
}
return originalPath
}),
)
map.sourcesContent = sourcesContent
const contents = JSON.stringify(map)
ctx.body = contents
ctx.status = 200
ctx.type = 'application/json'
}
}
================================================
FILE: bundless/src/middleware/static-serve.ts
================================================
import { Middleware } from 'koa'
import send, { SendOptions } from 'koa-send'
import { WEB_MODULES_PATH } from '../constants'
import { logger } from '../logger'
// like koa static but executes other middlewares after serving, needed to transform html afterwards
export function staticServeMiddleware(opts: SendOptions): Middleware {
opts.index = opts.index || 'index.html'
opts.hidden = opts.hidden || true
const cacheOptions: send.SendOptions = {
maxAge: 1000 * 60 * 60,
immutable: true,
}
return async function serve(ctx, next) {
if (ctx.method !== 'HEAD' && ctx.method !== 'GET') {
return next()
}
if (ctx.body) {
return next()
}
const isDep = ctx.path.includes(WEB_MODULES_PATH)
try {
logger.debug('Statically serving ' + ctx.path)
await send(ctx, ctx.path, { ...opts, ...(isDep && cacheOptions) })
} catch (err) {
if (err.status !== 404 && err.code !== 'ENOENT') {
throw new Error(`Cannot static serve ${ctx.path}: ${err}`)
}
}
await next()
}
}
================================================
FILE: bundless/src/plugins/assets.ts
================================================
import { NodeResolvePlugin } from '@esbuild-plugins/all'
import * as esbuild from 'esbuild'
import escapeStringRegexp from 'escape-string-regexp'
import fs from 'fs-extra'
import mime from 'mime-types'
import path from 'path'
import { defaultLoader } from '../constants'
import { PluginHooks } from '../plugins-executor'
import { fileToImportPath } from '../utils'
import { transform } from './esbuild'
export function AssetsPlugin({
loader: _loader,
}: {
loader?: Record<string, esbuild.Loader>
}) {
let loader = _loader || {}
loader = {
...defaultLoader,
...loader,
}
const extensions = Object.keys(loader)
const extensionsSet = new Set(extensions)
return {
name: 'assets',
setup: (hooks: PluginHooks) => {
const { onLoad, onResolve, ctx: { root, config } } = hooks
const filter = new RegExp(
'(' +
extensions
.filter((x) => x !== '.css') // css is handled in css plugin
.map(escapeStringRegexp)
.join('|') +
')$',
)
// what if an image is in another module and this resolver bypasses the node resolve plugin that runs the prebundle? maybe i need to throw? no because assets do not need to be optimized, i just need to make sure that node resolve is called before all other resolvers
NodeResolvePlugin({
name: 'assets-node-resolve',
isExtensionRequiredInImportPath: true,
extensions,
}).setup({
...hooks,
onLoad() {},
})
onLoad({ filter }, async (args) => {
const extension = path.extname(args.path)
if (!extensionsSet.has(extension)) {
return
}
const publicPath = fileToImportPath(root, args.path)
const loadedType = loader[extension]
if (loadedType === 'file') {
return {
contents: `export default ${JSON.stringify(
publicPath,
)}`,
}
}
let data = await await fs.readFile(args.path)
if (loadedType === 'js') {
return { contents: data.toString(), loader: 'js' }
}
if (
loadedType === 'jsx' ||
loadedType === 'ts' ||
loadedType === 'tsx'
) {
const res = await transform({
filePath: args.path,
src: data.toString(),
loader: loadedType,
config,
})
return {
contents: res.contents || '',
loader: 'js',
}
}
if (loadedType === 'base64') {
return {
contents: `export default "${data.toString('base64')}`,
loader: 'js',
}
}
if (loadedType === 'dataurl') {
const mimeType = mime.lookup(args.path)
return {
contents: `export default "data:${mimeType};base64,${data.toString(
'base64',
)}"`,
loader: 'js',
}
}
if (loadedType === 'text') {
return {
contents: `export default ${JSON.stringify(
data.toString(),
)}`,
loader: 'js',
}
}
if (loadedType === 'json') {
const transformed = await esbuild.transform(
data.toString(),
{
format: 'esm',
loader: 'json',
sourcefile: args.path,
},
)
return {
contents: transformed.code,
loader: 'js',
}
}
if (loadedType === 'binary') {
return {
contents: data.toString(), // how can i serve binary data to browser?
loader: 'js',
}
}
return null
})
},
}
}
================================================
FILE: bundless/src/plugins/buffer.ts
================================================
import * as esbuild from 'esbuild'
import { Plugin } from '../plugins-executor'
import { importPathToFile, readFile } from '../utils'
const BUFFER_PATH = '_bundless-node-buffer-polyfill_.js'
export function NodeBufferGlobal(): Plugin {
return {
name: 'buffer-global',
setup({ onResolve, onLoad, onTransform }) {
onTransform({ filter: /\.html$/ }, (args) => {
const contents = args.contents.replace(
/<body.*?>/,
`$&\n` +
`<script type="module" src="/${BUFFER_PATH}"></script>\n`,
)
return {
contents,
}
})
onResolve({ filter: new RegExp(BUFFER_PATH) }, (arg) => {
return {
path: BUFFER_PATH,
}
})
onLoad({ filter: new RegExp(BUFFER_PATH) }, async (arg) => {
const polyfill = await readFile(
require.resolve(
`@esbuild-plugins/node-globals-polyfill/Buffer.js`,
),
)
return {
contents: polyfill + `\nwindow.Buffer = Buffer;`,
loader: 'js',
}
})
},
}
}
================================================
FILE: bundless/src/plugins/css.ts
================================================
import { NodeResolvePlugin, resolveAsync } from '@esbuild-plugins/all'
import { transform } from 'esbuild'
import escapeStringRegexp from 'escape-string-regexp'
import hash_sum from 'hash-sum'
import path from 'path'
import fs from 'fs-extra'
import { CLIENT_PUBLIC_PATH, hmrPreamble } from '../constants'
import { PluginHooks } from '../plugins-executor'
import { osAgnosticPath } from '../utils'
const CSS_UTILS_PATH = '_bundless_css_utils.js'
/*
importing a css module file does 2 things
- import a js file that calls ensureCssLink and exports the class names as js object
- add the link in the html entry at build time
This way even if you load the app from a different entrypoint and you change location via history API, you get ensureCssLink that adds the link to the html
Global css files instead must be all loaded at once because its classnames are not unique
*/
export function CssPlugin({} = {}) {
return {
name: 'css',
setup: ({
ctx: { root, config, isBuild, graph },
onLoad,
onResolve,
onTransform,
}: PluginHooks) => {
// TODO use custom resolver that adds the .js extension to css paths?
async function cssResolver(args) {
try {
const res = await resolveAsync(args.path, {
basedir: args.resolveDir,
})
const virtualPath = res + '.cssjs'
if (res) {
return {
path: virtualPath,
}
}
} catch {}
}
onResolve({ filter: /\.css$/ }, cssResolver)
const cssExtensions = Object.keys(config.loader || {})
.filter((k) => config.loader?.[k] === 'css')
.map(escapeStringRegexp)
if (cssExtensions.length) {
onResolve(
{
filter: new RegExp(
'(' + cssExtensions.join('|') + ')$',
),
},
cssResolver,
)
}
onLoad({ filter: /\.cssjs$/ }, async (args) => {
try {
const css = await (
await fs.readFile(args.path.replace(/\.cssjs$/, ''))
).toString()
// const id = hash_sum(args.path)
let contents = await codegenCssForDev(css, args.path)
if (!isBuild) {
contents = hmrPreamble + '\n' + contents
}
return { contents, loader: 'js' }
} catch {}
})
// needed for other plugins that return css and are not resolved by this plugin
onTransform({ filter: /\.css$/ }, async (args) => {
let contents = await codegenCssForDev(args.contents, args.path)
if (!isBuild) {
contents = hmrPreamble + '\n' + contents
}
return { contents, loader: 'js' }
})
onResolve(
{ filter: new RegExp(escapeStringRegexp(CSS_UTILS_PATH)) },
(args) => {
return {
path: path.resolve(root, cssUtilsTemplate),
}
},
)
onLoad(
{ filter: new RegExp(escapeStringRegexp(CSS_UTILS_PATH)) },
(args) => {
return {
contents: cssUtilsTemplate,
loader: 'js',
}
},
)
},
}
}
const cssUtilsTemplate = `
function ensureCss(href) {
const existingLinkTags = document.getElementsByTagName('link')
for (let i = 0; i < existingLinkTags.length; i++) {
if (tag.rel === 'stylesheet' && tag.getAttribute('href') === href) {
return
}
}
const linkTag = document.createElement('link')
linkTag.rel = 'stylesheet'
linkTag.type = 'text/css'
linkTag.href = href
const head = document.getElementsByTagName('head')[0]
head.appendChild(linkTag)
}
`
export async function codegenCssForDev(
css: string,
sourcefile: string,
modules?: Record<string, string>,
) {
let code = `
const css = ${JSON.stringify(css)};
if (typeof document !== 'undefined') {
import.meta.hot.accept();
import.meta.hot.dispose(() => {
document.head.removeChild(styleEl);
});
const styleEl = document.createElement("style");
const codeEl = document.createTextNode(css);
styleEl.type = 'text/css';
styleEl.appendChild(codeEl);
document.head.appendChild(styleEl);
}
`
if (modules) {
const transformed = await transform(JSON.stringify(modules), {
format: 'esm',
loader: 'json',
sourcefile,
})
code += transformed.code
} else {
code += `export default css`
}
return code
}
export function codegenCssForProduction(
cssPath: string,
modules?: Record<string, string>,
): string {
let code =
hmrPreamble +
`
import { ensureCSS } from '${CSS_UTILS_PATH}'
if (typeof window !== 'undefined') {
ensureCSS(${JSON.stringify(cssPath)})
}
`
return code
}
================================================
FILE: bundless/src/plugins/env.ts
================================================
import dotenv from 'dotenv'
import dotenvExpand from 'dotenv-expand'
import findUp from 'find-up'
import fs from 'fs-extra'
import path from 'path'
import { logger } from '../logger'
import { PluginHooks } from '../plugins-executor'
export function EnvPlugin({
envFiles = [] as string[],
env = {} as Record<string, string>,
findUp: isFindUp = false,
} = {}) {
return {
name: 'env',
setup: ({ initialOptions, ctx: { root } }: PluginHooks) => {
let define = {}
for (let _envFile of envFiles) {
let envFile
if (fs.existsSync(path.resolve(root, _envFile))) {
envFile = path.resolve(root, _envFile)
} else if (isFindUp) {
envFile = findUp.sync(_envFile, { cwd: root }) || ''
}
if (!envFile) {
logger.warn(`Cannot find env file '${_envFile}'`)
continue
}
const data = fs.readFileSync(envFile).toString()
const parsed = dotenv.parse(data, {
debug: !!process.env.DEBUG || undefined,
})
// let environment variables use each other
dotenvExpand({
parsed,
// prevent process.env mutation
ignoreProcessEnv: true,
} as any)
for (const k in parsed) {
define[`process.env.${k}`] = JSON.stringify(parsed[k])
}
}
for (const k in env) {
define[`process.env.${k}`] = JSON.stringify(env[k])
}
Object.assign(initialOptions.define, define)
},
}
}
================================================
FILE: bundless/src/plugins/esbuild.ts
================================================
import chalk from 'chalk'
import * as esbuild from 'esbuild'
import { Loader, Message, TransformOptions } from 'esbuild'
import path from 'path'
import { Config } from '../config'
import { OnTransformResult, PluginHooks } from '../plugins-executor'
import { generateDefineObject } from '../prebundle/esbuild'
import { generateCodeFrame } from '../utils'
export function EsbuildTransformPlugin({} = {}) {
return {
name: 'esbuild-transform',
setup: ({ onTransform, onClose, ctx: { config } }: PluginHooks) => {
onTransform({ filter: /\.(tsx?|jsx)$/ }, async (args) => {
// do not transpile again if already transpiled
if (args.loader === 'js') {
return
}
return transform({
src: args.contents,
filePath: args.path,
config,
})
})
},
}
}
const JsxPresets: Record<
string,
Pick<TransformOptions, 'jsxFactory' | 'jsxFragment'>
> = {
vue: { jsxFactory: 'jsx', jsxFragment: 'Fragment' },
preact: { jsxFactory: 'h', jsxFragment: 'Fragment' },
react: {},
// react: { jsxFactory: 'React.createElement', }, // use esbuild default
}
export function resolveJsxOptions(options: Config['jsx'] = 'react') {
if (typeof options === 'string') {
if (!(options in JsxPresets)) {
console.error(`unknown jsx preset: '${options}'.`)
}
return JsxPresets[options] || {}
} else if (options) {
return {
jsxFactory: options.factory,
jsxFragment: options.fragment,
}
}
}
// transform used in server plugins with a more friendly API
export const transform = async ({
src,
filePath,
loader,
config,
}: {
src: string
filePath: string
config?: Config
loader?: esbuild.Loader
exitOnFailure?: boolean
}): Promise<OnTransformResult> => {
const options: TransformOptions = {
loader: loader || (path.extname(filePath).slice(1) as Loader),
logLevel: 'error',
sourcemap: true,
// format: 'esm', // passing format reorders exports https://github.com/evanw/esbuild/issues/710
// ensure source file name contains full query
sourcefile: filePath,
// TODO use define object here? this way it works the same as in build, but this way it won't work when using another transformer
target: 'es2020',
...resolveJsxOptions(config?.jsx),
}
try {
const result = await esbuild.transform(src, options)
let contents = result.code
// if transpiling (j|t)sx file, inject the imports for the jsx helper and
// Fragment.
if (filePath.endsWith('x')) {
// if (!jsxOption || jsxOption === 'vue') {
// code +=
// `\nimport { jsx } from '${vueJsxPublicPath}'` +
// `\nimport { Fragment } from 'vue'`
// }
if (config?.jsx === 'preact') {
contents += `\nimport { h, Fragment } from 'preact'`
}
}
return {
contents,
map: JSON.parse(result.map),
}
} catch (e) {
if (e.errors) {
e.errors.forEach((m: Message) => printMessage(m, src))
} else {
console.error(e)
}
throw new Error(
`Error while transforming ${filePath} with esbuild: ${e}`,
)
}
}
function printMessage(m: Message, code: string) {
console.error(chalk.yellow(m.text))
if (m.location) {
const lines = code.split(/\r?\n/g)
const line = Number(m.location.line)
const column = Number(m.location.column)
const offset =
lines
.slice(0, line - 1)
.map((l) => l.length)
.reduce((total, l) => total + l + 1, 0) + column
console.error(generateCodeFrame(code, offset, offset + 1))
}
}
================================================
FILE: bundless/src/plugins/hmr-client.ts
================================================
import fs from 'fs-extra'
import { CLIENT_PUBLIC_PATH, hmrClientNamespace } from '../constants'
import { PluginHooks } from '../plugins-executor'
import { generateDefineObject } from '../prebundle/esbuild'
export const clientFilePath = require.resolve('../../esm/client/template.js')
export const sourceMapSupportPath =
'__source-map-support.js?namespace=source-map-support'
export function HmrClientPlugin({ getPort }) {
return {
name: 'hmr-client',
setup: ({
onLoad,
onTransform,
ctx: { config, root },
}: PluginHooks) => {
onTransform({ filter: /\.html$/ }, (args) => {
const contents = args.contents.replace(
/<body.*?>/,
`$&\n` +
`<script type="module" src="${CLIENT_PUBLIC_PATH}"></script>\n`,
)
return {
contents,
}
})
onLoad(
{ filter: /.*/, namespace: 'source-map-support' },
async () => {
return {
contents: await fs.readFile(
require.resolve(
'source-map-support/browser-source-map-support.js',
),
),
}
},
)
onLoad(
{ filter: /.*/, namespace: hmrClientNamespace },
async (args) => {
const defines = generateDefineObject({ config })
const clientCode = fs
.readFileSync(clientFilePath, 'utf-8')
.replace(
`__DEFINES__`,
'{\n' +
Object.keys(defines)
.sort((a, b) => a.length - b.length)
.map(
(k) =>
` ${JSON.stringify(k)}: ${
defines[k]
},`,
)
.join('\n') +
'\n}',
)
.replace(`//# sourceMappingURL=`, '//')
let socketPort: number | string = getPort()
// infer on client by default
let socketProtocol: any = null
let socketHostname: any = null
let socketTimeout = 30000
const hmrConfig = config.server?.hmr || true
if (hmrConfig && typeof hmrConfig === 'object') {
// hmr option has highest priory
socketProtocol = hmrConfig.protocol || null
socketHostname = hmrConfig.hostname || null
socketPort = hmrConfig.port || getPort()
if (hmrConfig.timeout) {
socketTimeout = hmrConfig.timeout
}
if (hmrConfig.path) {
socketPort = `${socketPort}/${hmrConfig.path}`
}
}
return {
contents: clientCode
.replace(
`__HMR_PROTOCOL__`,
JSON.stringify(socketProtocol),
)
.replace(
`__HMR_HOSTNAME__`,
JSON.stringify(socketHostname),
)
.replace(`__HMR_PORT__`, JSON.stringify(socketPort))
.replace(
`__HMR_ENABLE_OVERLAY__`,
JSON.stringify(true),
)
.replace(
`__HMR_TIMEOUT__`,
JSON.stringify(socketTimeout),
),
}
},
)
},
}
}
================================================
FILE: bundless/src/plugins/html-ingest.ts
================================================
import fs from 'fs'
import posthtml, { Node, Plugin as PosthtmlPlugin } from 'posthtml'
import path from 'path'
import { Plugin } from '../plugins-executor'
import { cleanUrl } from '../utils'
import slash from 'slash'
const NAME = 'html-ingest'
interface Options {
name?: string
root: string // to resolve paths in case the html page is not in root
transformImportPath?: (importPath: string) => string
// emitHtml?: (arg: { path: string; html: string }) => Promise<void>
}
/**
* Let you use html files as entrypoints for esbuild
*/
export function HtmlIngestPlugin({
name = NAME,
root,
transformImportPath,
}: Options): Plugin {
return {
name,
setup: function setup({ onLoad, onTransform, onResolve }) {
onTransform({ filter: /\.html$/ }, async (args) => {
try {
const html = args.contents
const jsUrls = await getHtmlScriptsUrls(html)
// const folder = path.relative(root, path.dirname(args.path))
const pathToRoot = slash(
path.relative(path.dirname(args.path), root),
)
const contents = jsUrls
.map((importPath) => {
// src='/file.js' -> ../../file.js
if (importPath.startsWith('/')) {
importPath = path.posix.join(
pathToRoot,
'.' + importPath,
)
}
// src='file.js' -> ./file.js
if (bareImportRE.test(importPath)) {
importPath = './' + importPath
}
return importPath
})
.map((x) =>
transformImportPath ? transformImportPath(x) : x,
)
.map((importPath) => `export * from '${importPath}'`)
.join('\n')
return {
loader: 'js',
contents,
}
} catch (e) {
throw new Error(`Cannot transform html ${args.path}, ${e}`)
}
})
},
}
}
export async function getHtmlScriptsUrls(html: string) {
const urls: string[] = []
const transformer = posthtml([
(tree) => {
tree.walk((node) => {
if (
node &&
node.tag === 'script' &&
node.attrs &&
node.attrs['type'] === 'module' &&
node.attrs['src'] &&
isRelative(node.attrs['src'])
) {
urls.push(node.attrs['src'])
}
return node
})
},
])
try {
await transformer.process(html)
} catch (e) {
throw new Error(`Cannot process html with posthtml: ${e}\n${html}`)
}
return urls.filter(Boolean)
}
const bareImportRE = /^[^\/\.]/
function isRelative(x: string) {
x = cleanUrl(x)
return bareImportRE.test(x) || x.startsWith('.') || x.startsWith('/')
}
================================================
FILE: bundless/src/plugins/html-resolver.ts
================================================
import fs from 'fs-extra'
import path from 'path'
import { PluginHooks } from '../plugins-executor'
export function HtmlResolverPlugin({} = {}) {
return {
name: 'html-resolver',
setup: ({ ctx: { root }, onLoad, onResolve }: PluginHooks) => {
onResolve({ filter: /\.html/ }, async (args) => {
args.path = path.resolve(root, args.path)
var resolved = path.resolve(args.resolveDir || root, args.path)
if (resolved && fs.existsSync(resolved)) {
return {
path: resolved,
}
}
const relativePath = path.relative(root, args.path)
var resolved = path.resolve(
path.resolve(root, path.join('public', relativePath)),
)
if (resolved && fs.existsSync(resolved)) {
return {
path: resolved,
}
}
return null
})
onLoad({ filter: /\.html$/ }, async (args) => {
try {
const realFilePath = args.path // .replace('.html.js', '.html')
const html = await (
await fs.readFile(realFilePath, {
encoding: 'utf-8',
})
).toString()
return {
contents: html,
loader: 'html' as any,
}
} catch (e) {
return null
throw new Error(`Cannot load ${args.path}, ${e}`)
}
})
},
}
}
================================================
FILE: bundless/src/plugins/html-transform.ts
================================================
import posthtml, { Plugin } from 'posthtml'
import { PluginHooks } from '../plugins-executor'
import { cleanUrl } from '../utils'
export function HtmlTransformUrlsPlugin({
transforms,
}: {
transforms: Plugin<any>[]
}) {
return {
name: 'html-transform-urls',
setup: ({ onTransform }: PluginHooks) => {
onTransform({ filter: /\.html$/ }, async (args) => {
const transformer = posthtml([...transforms])
const result = await transformer.process(args.contents)
const contents = result.html
return { contents }
})
},
}
}
// TODO transformer to rewrite inline script imports
================================================
FILE: bundless/src/plugins/index.ts
================================================
export { EsbuildTransformPlugin } from './esbuild'
export { RewritePlugin } from './rewrite'
export { CssPlugin } from './css'
export { ResolveSourcemapPlugin } from './resolve-sourcemaps'
export { HmrClientPlugin } from './hmr-client'
export { JSONPlugin } from './json'
export { AssetsPlugin } from './assets'
export { UrlResolverPlugin } from './url-resolver'
export { HtmlTransformUrlsPlugin } from './html-transform'
export { HtmlResolverPlugin } from './html-resolver'
export { HtmlIngestPlugin } from './html-ingest'
export { SourceMapSupportPlugin } from './source-map-support'
export { EnvPlugin } from './env'
export { NodeBufferGlobal } from './buffer'
export {
NodeModulesPolyfillPlugin,
NodeResolvePlugin,
NodeGlobalsPolyfillPlugin
} from '@esbuild-plugins/all'
================================================
FILE: bundless/src/plugins/json.ts
================================================
import { NodeResolvePlugin } from '@esbuild-plugins/all'
import { transform } from 'esbuild'
import { PluginHooks } from '../plugins-executor'
import { readFile } from '../utils'
export function JSONPlugin({} = {}) {
return {
name: 'json',
setup: (hooks: PluginHooks) => {
const { onLoad, onResolve } = hooks
NodeResolvePlugin({
name: 'json-node-resolve',
isExtensionRequiredInImportPath: true,
extensions: ['.json'],
}).setup({
...hooks,
onLoad() {},
})
onLoad({ filter: /\.json$/ }, async (args) => {
const json = await readFile(args.path)
const transformed = await transform(json, {
format: 'esm',
loader: 'json',
sourcefile: args.path,
})
const contents = transformed.code
return { contents }
})
},
}
}
================================================
FILE: bundless/src/plugins/resolve-sourcemaps.ts
================================================
import chalk from 'chalk'
import fs from 'fs'
import path from 'path'
import { RawSourceMap } from 'source-map'
import { PluginHooks } from '../plugins-executor'
import { fileToImportPath, jsTypeRegex, readFile } from '../utils'
const sourcemapRegex = /\/\/#\ssourceMappingURL=([\w\d-_\.]+)\n*$/
export function ResolveSourcemapPlugin({} = {}) {
return {
name: 'resolve-sourcemaps',
setup: ({
onTransform,
pluginsExecutor,
ctx: { root },
}: PluginHooks) => {
onTransform({ filter: jsTypeRegex }, async (args) => {
let contents = args.contents
const match = contents.match(sourcemapRegex)
if (!match) {
return
}
let filePath = match[1]
if (!filePath || filePath.startsWith('data:')) {
// TODO skip other data: non base64 formats in sourcemaps
return
}
if (!filePath.startsWith('.') && !filePath.startsWith('/')) {
filePath = './' + filePath
}
const resolved = await pluginsExecutor.resolve({
importer: args.path,
path: filePath.trim(),
namespace: '',
resolveDir: path.dirname(args.path),
})
if (!resolved?.path) {
return
}
contents = contents.replace(
sourcemapRegex,
`//# sourceMappingURL=${fileToImportPath(
root,
resolved?.path,
)}`,
)
return {
contents,
}
})
},
}
}
================================================
FILE: bundless/src/plugins/rewrite/__snapshots__/commonjs.test.ts.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`rewrite commonjs imports 0 "import React from 'react'" 1`] = `"import react_cjsImport0 from \\"react\\"; const React = react_cjsImport0 && react_cjsImport0.__esModule ? react_cjsImport0.default : react_cjsImport0;"`;
exports[`rewrite commonjs imports 1 "import * as React from 'react'" 1`] = `"import react_cjsImport0 from \\"react\\"; const React = {default: react_cjsImport0, ...(typeof react_cjsImport0 === 'object' && react_cjsImport0)};"`;
exports[`rewrite commonjs imports 2 "import React, { useState } from 'react'" 1`] = `"import react_cjsImport0 from \\"react\\"; const React = react_cjsImport0 && react_cjsImport0.__esModule ? react_cjsImport0.default : react_cjsImport0; const useState = react_cjsImport0[\\"useState\\"];"`;
exports[`rewrite commonjs imports 3 "import { useState } from 'react'" 1`] = `"import react_cjsImport0 from \\"react\\"; const useState = react_cjsImport0[\\"useState\\"];"`;
exports[`rewrite commonjs imports 4 "import { useState, useEffect } from 'react'" 1`] = `"import react_cjsImport0 from \\"react\\"; const useState = react_cjsImport0[\\"useState\\"]; const useEffect = react_cjsImport0[\\"useEffect\\"];"`;
exports[`rewrite commonjs imports 5 "import { useState as something } from 'react'" 1`] = `"import react_cjsImport0 from \\"react\\"; const something = react_cjsImport0[\\"useState\\"];"`;
exports[`rewrite commonjs imports 6 "import { useState as something, useEffect } from 'react'" 1`] = `"import react_cjsImport0 from \\"react\\"; const something = react_cjsImport0[\\"useState\\"]; const useEffect = react_cjsImport0[\\"useEffect\\"];"`;
exports[`rewrite commonjs imports 7 "import { useState as something, useEffect as alias } from 'react'" 1`] = `"import react_cjsImport0 from \\"react\\"; const something = react_cjsImport0[\\"useState\\"]; const alias = react_cjsImport0[\\"useEffect\\"];"`;
exports[`rewrite commonjs imports 8 "import { default as Default } from 'react'" 1`] = `"import react_cjsImport0 from \\"react\\"; const Default = react_cjsImport0 && react_cjsImport0.__esModule ? react_cjsImport0.default : react_cjsImport0;"`;
exports[`rewrite commonjs imports 9 "import { default as Default, useEffect } from 'react'" 1`] = `"import react_cjsImport0 from \\"react\\"; const Default = react_cjsImport0 && react_cjsImport0.__esModule ? react_cjsImport0.default : react_cjsImport0; const useEffect = react_cjsImport0[\\"useEffect\\"];"`;
================================================
FILE: bundless/src/plugins/rewrite/commonjs.test.ts
================================================
import { parse } from '../../utils'
import { transformCjsImport } from './commonjs'
describe('rewrite commonjs imports', () => {
const cases = [
`import React from 'react'`,
`import * as React from 'react'`,
`import React, { useState } from 'react'`,
`import { useState } from 'react'`,
`import { useState, useEffect } from 'react'`,
`import { useState as something } from 'react'`,
`import { useState as something, useEffect } from 'react'`,
`import { useState as something, useEffect as alias } from 'react'`,
`import { default as Default } from 'react'`,
`import { default as Default, useEffect } from 'react'`,
]
for (let [i, testCase] of cases.entries()) {
test(`${i} "${testCase}"`, () => {
const res = transformCjsImport(testCase, 'react', 'react', 0)
expect(res).not.toContain('\n')
parse(res) // check that it's valid code
expect(res).toMatchSnapshot()
})
}
})
================================================
FILE: bundless/src/plugins/rewrite/commonjs.ts
================================================
import { ImportDeclaration } from '@babel/types'
import fs from 'fs-extra'
import { isPlainObject } from 'lodash'
import memoize from 'micro-memoize'
import path from 'path'
import { COMMONJS_ANALYSIS_PATH, WEB_MODULES_PATH } from '../../constants'
import { logger } from '../../logger'
import { onResolveLock } from '../../serve'
import { makeLegalIdentifier, osAgnosticPath, parse } from '../../utils'
export interface OptimizeAnalysisResult {
isCommonjs: { [name: string]: true }
}
/**
* read analysis result from optimize step
* If we can't find analysis result, return null
* (maybe because user set optimizeDeps.auto to false)
*/
export const getAnalysis = memoize(function getAnalysis(
root: string,
): OptimizeAnalysisResult | null {
let analysis: OptimizeAnalysisResult | null
try {
analysis = fs.readJsonSync(path.resolve(root, COMMONJS_ANALYSIS_PATH))
} catch (error) {
logger.debug(
`Cannot find commonjs analysis at ${path.resolve(
root,
COMMONJS_ANALYSIS_PATH,
)}`,
)
analysis = null
}
if (analysis && !isPlainObject(analysis.isCommonjs)) {
throw new Error(`invalid ${COMMONJS_ANALYSIS_PATH}`)
}
logger.debug(
`Got new commonjs analysis: ${JSON.stringify(
analysis?.isCommonjs,
null,
4,
)}`,
)
return analysis
})
export function clearCommonjsAnalysisCache() {
logger.debug(`Invalidating commonjs cache`)
getAnalysis.cache.keys.length = 0
getAnalysis.cache.values.length = 0
}
export function isOptimizedCjs(root: string, filename: string) {
if (!onResolveLock.isReady) {
throw new Error(
`Cannot call isOptimizedCjs when onResolveLock is locked!`,
)
}
const analysis = getAnalysis(root)
if (!analysis) {
return false
}
const isCommonjs = !!analysis.isCommonjs[osAgnosticPath(filename, root)]
return isCommonjs
}
type ImportNameSpecifier = { importedName: string; localName: string }
// todo if module has __esModule and there is only a default import, transform to .default, -> const imported = realImport.__esModule ? realImport.default : realImport
export function transformCjsImport(
exp: string,
id: string,
resolvedPath: string,
importIndex: number,
): string {
const ast = parse(exp)[0] as ImportDeclaration
const importNames = getImportNames(ast)
return generateCjsImport(importNames, id, resolvedPath, importIndex)
}
function getImportNames(ast: ImportDeclaration) {
const importNames: ImportNameSpecifier[] = []
ast.specifiers.forEach((obj) => {
if (
obj.type === 'ImportSpecifier' &&
obj.imported.type === 'Identifier'
) {
const importedName = obj.imported.name
const localName = obj.local.name
importNames.push({ importedName, localName })
} else if (obj.type === 'ImportDefaultSpecifier') {
importNames.push({
importedName: 'default',
localName: obj.local.name,
})
} else if (obj.type === 'ImportNamespaceSpecifier') {
importNames.push({ importedName: '*', localName: obj.local.name })
}
})
return importNames
}
function generateCjsImport(
importNames: ImportNameSpecifier[],
id: string,
resolvedPath: string,
importIndex: number,
): string {
// If there is multiple import for same id in one file,
// importIndex will prevent the cjsModuleName to be duplicate
const cjsModuleName = makeLegalIdentifier(`${id}_cjsImport${importIndex}`)
const lines: string[] = [`import ${cjsModuleName} from "${resolvedPath}";`]
importNames.forEach(({ importedName, localName }) => {
// __esModule means the module has been compiled from ESM: ESM -> commonjs -> ESM
// we consider commonjs all modules with only a default export, but if the module has been compiled from ESM, it will contain double default export: default.default
if (importedName === 'default') {
lines.push(
`const ${localName} = ${cjsModuleName} && ${cjsModuleName}.__esModule ? ${cjsModuleName}.default : ${cjsModuleName};`,
)
} else if (importedName === '*') {
lines.push(
`const ${localName} = {default: ${cjsModuleName}, ...(typeof ${cjsModuleName} === 'object' && ${cjsModuleName})};`,
)
} else {
lines.push(
`const ${localName} = ${cjsModuleName}["${importedName}"];`,
)
}
})
return lines.join(' ')
}
// adds the default export to the namespace in case this is an iterable object, this is to support the case `import * as namespace from 'mod'; namespace.default()`
// TODO namespace imports can be polluted in case default import is an object and user is doing import * on a ES module with only a default export, this can be solved adding isCommonjs to esbuild metafile
export function generateNamespaceExport(mId: string) {
return `({...${mId}, ...(${mId}.default instanceof Object && ${mId}.default.constructor === Object && m.default)})`
}
================================================
FILE: bundless/src/plugins/rewrite/index.ts
================================================
export * from './rewrite'
================================================
FILE: bundless/src/plugins/rewrite/rewrite.ts
================================================
import chalk from 'chalk'
import { ImportSpecifier, parse as parseImports } from 'es-module-lexer'
import MagicString from 'magic-string'
import path from 'path'
import { CLIENT_PUBLIC_PATH, hmrPreamble } from '../../constants'
import { HmrGraph } from '../../hmr-graph'
import { logger } from '../../logger'
import { PluginHooks, PluginsExecutor } from '../../plugins-executor'
import { onResolveLock } from '../../serve'
import {
appendQuery,
cleanUrl,
fileToImportPath,
isExternalUrl,
jsTypeRegex,
osAgnosticPath,
} from '../../utils'
import {
generateNamespaceExport,
isOptimizedCjs,
transformCjsImport,
} from './commonjs'
export function RewritePlugin({ filter = jsTypeRegex } = {}) {
return {
name: 'rewrite',
setup: ({
onTransform,
pluginsExecutor,
ctx: { graph, config, root, isBuild },
}: PluginHooks) => {
if (config.platform !== 'browser') {
return
}
if (isBuild || !graph) {
return
}
onTransform({ filter }, async (args) => {
const { contents, map } = await rewriteImports({
graph,
namespace: args.namespace || 'file',
importer: args.path,
root,
pluginsExecutor,
source: args.contents,
})
return {
contents, // TODO module rewrite needs not need sourcemaps? How?
map,
}
})
},
}
}
export async function rewriteImports({
source,
importer,
graph,
pluginsExecutor,
namespace,
root,
}: {
source: string
namespace: string
importer: string
pluginsExecutor: PluginsExecutor
root: string
graph: HmrGraph
}): Promise<{ contents: string; map?: any }> {
// strip UTF-8 BOM
if (source.charCodeAt(0) === 0xfeff) {
source = source.slice(1)
}
const relativeImporter = osAgnosticPath(importer, root)
// TODO how are computed files path removed?
graph.ensureEntry(importer)
try {
await onResolveLock.wait()
let imports: ImportSpecifier[] = []
try {
imports = parseImports(source)[0]
} catch (e) {
throw new Error(
`Failed to parse ${chalk.cyan(
importer,
)} for import rewrite.\nIf you are using ` +
`JSX, make sure to named the file with the .jsx extension.`,
)
}
const isHmrEnabled = source.includes('import.meta.hot')
const hasEnv = source.includes('import.meta.env')
if (!imports.length && !isHmrEnabled && !hasEnv) {
return { contents: source }
}
const magicString = new MagicString(source)
if (isHmrEnabled) {
magicString.prepend(hmrPreamble)
}
const currentNode = graph.ensureEntry(importer, {
isHmrEnabled,
importees: new Set(),
})
for (let i = 0; i < imports.length; i++) {
const {
s: start,
e: end,
d: dynamicIndex,
ss: expStart,
se: expEnd,
} = imports[i]
let id = source.substring(start, end)
const hasIgnore = /\/\*\s*@bundless-ignore\s*\*\//.test(id)
let hasLiteralDynamicId = false
const isDynamicImport = dynamicIndex >= 0
if (isDynamicImport) {
id = id.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '')
const literalIdMatch = id.match(
/^\s*(?:'([^']+)'|"([^"]+)")\s*$/,
)
if (literalIdMatch) {
hasLiteralDynamicId = true
id = literalIdMatch[1] || literalIdMatch[2]
}
}
if (dynamicIndex === -1 || hasLiteralDynamicId) {
// do not rewrite external imports
if (isExternalUrl(id)) {
continue
}
const resolveResult = await pluginsExecutor.resolve({
importer,
namespace,
resolveDir: path.dirname(importer),
path: id,
})
if (!resolveResult || !resolveResult.path) {
// do not fail on unresolved dynamic imports
if (isDynamicImport) {
logger.log(
`Cannot resolve '${id}' from '${relativeImporter}'`,
)
continue
}
throw new Error(
`Cannot resolve '${id}' from '${relativeImporter}'`,
)
}
if (resolveResult?.pluginData) {
logger.warn(
`esbuild pluginData is not supported by bundless, used by plugin ${resolveResult.pluginName}`,
)
}
let resolvedImportPath = ''
const isVirtual =
resolveResult.namespace &&
resolveResult.namespace !== 'file'
// handle bare imports like node builtins, virtual files, ...
if (isVirtual || !path.isAbsolute(resolveResult.path || '')) {
resolvedImportPath = '/' + resolveResult.path
} else {
resolvedImportPath = fileToImportPath(
root,
resolveResult?.path || '',
)
}
const newNamespace = encodeURIComponent(
resolveResult.namespace || namespace,
)
resolvedImportPath = appendQuery(
resolvedImportPath,
`namespace=${newNamespace}`,
)
// TODO maybe also register virtual files, ok onFileChange will never get triggered but maybe there is virtual css file or stuff like that that needs to be updated?
if (!isVirtual) {
const importeeNode = graph.ensureEntry(resolveResult.path)
// do not use stale modules
resolvedImportPath = appendQuery(
resolvedImportPath,
`t=${
importeeNode.hash + importeeNode.lastUsedTimestamp
}`,
)
}
if (resolvedImportPath !== id) {
if (isOptimizedCjs(root, resolveResult.path || '')) {
if (dynamicIndex === -1) {
const exp = source.substring(expStart, expEnd)
const replacement = transformCjsImport(
exp,
id,
resolvedImportPath,
i,
)
magicString.overwrite(expStart, expEnd, replacement)
} else if (hasLiteralDynamicId) {
// rewrite `import('package')` to
// import('/package').then(m=>({...((m.default instanceof Object && m.default.constructor === Object) && m.default), ...m})));
magicString.overwrite(
dynamicIndex,
end + 1,
`import('${resolvedImportPath}').then(m=>${generateNamespaceExport(
'm',
)})`,
)
}
} else {
magicString.overwrite(
start,
end,
hasLiteralDynamicId
? `'${resolvedImportPath}'`
: resolvedImportPath,
)
}
}
// save the import chain for hmr analysis
const cleanImportee = cleanUrl(resolvedImportPath)
if (
// no need to track hmr client or module dependencies
cleanImportee !== CLIENT_PUBLIC_PATH
) {
currentNode.importees.add(cleanImportee)
}
} else if (id !== 'import.meta' && !hasIgnore) {
logger.log(
chalk.yellow(
`Cannot rewrite dynamic import(${id}) in ${relativeImporter}.`,
),
)
}
}
return {
contents: magicString.toString(),
map: undefined, // do i really need sourcemaps? code is readable enough
}
} catch (e) {
e.message = `Invalid module ${relativeImporter}\n` + e
throw e
}
}
================================================
FILE: bundless/src/plugins/source-map-support.ts
================================================
import fs from 'fs-extra'
import { CLIENT_PUBLIC_PATH } from '../constants'
import { PluginHooks } from '../plugins-executor'
export const sourceMapSupportPath =
'__source-map-support.js?namespace=source-map-support'
export function SourceMapSupportPlugin({} = {}) {
return {
name: 'hmr-client',
setup: ({
onLoad,
onTransform,
ctx: { config, root },
}: PluginHooks) => {
// TODO reenable source map support
return
onTransform({ filter: /\.html$/ }, (args) => {
const contents = args.contents.replace(
/<body.*?>/,
`$&\n` +
`<script src="/${sourceMapSupportPath}"></script>\n` +
`<script>window.sourceMapSupport = sourceMapSupport; sourceMapSupport.install();</script>\n`,
)
return {
contents,
}
})
onLoad(
{ filter: /.*/, namespace: 'source-map-support' },
async () => {
return {
contents: await fs.readFile(
require.resolve(
'source-map-support/browser-source-map-support.js',
),
),
}
},
)
},
}
}
================================================
FILE: bundless/src/plugins/url-resolver.ts
================================================
import { NodeResolvePlugin } from '@esbuild-plugins/all'
import { PluginHooks } from '../plugins-executor'
import { importPathToFile, readFile } from '../utils'
import url from 'url'
import { logger } from '../logger'
import qs from 'qs'
export function UrlResolverPlugin({} = {}) {
return {
name: 'url-resolver',
setup: ({ ctx: { root }, onResolve }: PluginHooks) => {
onResolve({ filter: /\?/ }, async (arg) => {
if (!arg.path.includes('?')) {
return
}
const parsed = url.parse(arg.path)
if (!parsed.pathname) {
throw new Error('no pathname in ' + arg.path)
}
const query = qs.parse(parsed.query || '')
if (
query.namespace &&
typeof query.namespace === 'string' &&
query.namespace !== 'file'
) {
// logger.log(`Removed query from path ${arg.path}`)
return {
path: parsed.pathname.slice(1), // TODO write a spec for virtual files in url behaviour
namespace: query.namespace,
}
}
return {
path: importPathToFile(root, parsed.pathname),
}
})
},
}
}
================================================
FILE: bundless/src/plugins-executor.ts
================================================
import { O_TRUNC } from 'constants'
import * as esbuild from 'esbuild'
import { cloneDeep } from 'lodash'
import { promises } from 'fs-extra'
import { Config } from './config'
import url from 'url'
import fs from 'fs-extra'
import { HmrGraph } from './hmr-graph'
import { logger } from './logger'
import { flatten, osAgnosticPath } from './utils'
import qs from 'qs'
import { mergeSourceMap } from './utils/sourcemaps'
import path from 'path'
import { ansiChart } from './utils/profiling'
import { FSWatcher } from 'chokidar'
import { resolveAsync } from '@esbuild-plugins/all'
import { MAIN_FIELDS } from './constants'
export interface Plugin {
name: string
modulesToPrebundle?: string[]
enforce?: 'pre' | 'post'
setup: (build: PluginHooks) => void
}
type OnResolveCallback = (
args: esbuild.OnResolveArgs,
) => Maybe<esbuild.OnResolveResult | Promise<Maybe<esbuild.OnResolveResult>>>
type OnLoadCallback = (
args: esbuild.OnLoadArgs,
) => Maybe<esbuild.OnLoadResult | Promise<Maybe<esbuild.OnLoadResult>>>
type OnTransformCallback = (
args: OnTransformArgs,
) => Maybe<OnTransformResult | Promise<Maybe<OnTransformResult>>>
type OnCloseCallback = () => void | Promise<void>
export interface PluginsExecutorCtx {
config: Config
root: string
graph?: HmrGraph
isBuild: boolean
watcher?: FSWatcher
}
export interface PluginHooks extends esbuild.PluginBuild {
ctx: PluginsExecutorCtx
pluginsExecutor: PluginsExecutor
onResolve(
options: esbuild.OnResolveOptions,
callback: OnResolveCallback,
): void
onLoad(options: esbuild.OnLoadOptions, callback: OnLoadCallback): void
onTransform(
options: esbuild.OnLoadOptions,
callback: OnTransformCallback,
): void
onClose(options: any, callback: OnCloseCallback): void
}
export interface OnTransformArgs {
path: string
loader: esbuild.Loader
namespace?: string
contents: string
}
export interface OnTransformResult {
contents: string
map?: any
loader?: esbuild.Loader
}
type Maybe<x> = x | undefined | null
type PluginInternalObject<CB> = {
name: string
options: { filter: RegExp; namespace?: string }
callback: CB
}
export type OnResolved = (
result: esbuild.OnResolveResult & { importer: string },
) => Promise<Maybe<esbuild.OnResolveResult>> | Maybe<esbuild.OnResolveResult>
// TODO let plugins modify the options, pass an esbuild options as argument and you can access the mutated version as class instance
export class PluginsExecutor {
ctx: PluginsExecutorCtx
plugins: Plugin[]
isProfiling: boolean
onResolved?: OnResolved
initialOptions: esbuild.BuildOptions
private startingInitialOptions: esbuild.BuildOptions
private transforms: PluginInternalObject<OnTransformCallback>[] = []
private resolvers: PluginInternalObject<OnResolveCallback>[] = []
private loaders: PluginInternalObject<OnLoadCallback>[] = []
private closers: PluginInternalObject<OnCloseCallback>[] = []
constructor(_args: {
plugins: Array<Plugin | esbuild.Plugin>
ctx: PluginsExecutorCtx
initialOptions: esbuild.BuildOptions
isProfiling?: boolean
onResolved?: OnResolved
}) {
const {
ctx,
plugins,
isProfiling = false,
onResolved,
initialOptions,
} = _args
this.ctx = ctx
this.initialOptions = initialOptions
this.startingInitialOptions = cloneDeep(initialOptions)
this.onResolved = onResolved
this.plugins = plugins
this.isProfiling = isProfiling
for (let plugin of plugins) {
if (isProfiling) {
plugin = this.wrapPluginForProfiling(plugin)
}
const { name, setup } = plugin
setup({
ctx,
initialOptions,
pluginsExecutor: this,
onLoad: (options, callback) => {
this.loaders.push({ options, callback, name })
},
onResolve: (options, callback) => {
this.resolvers.push({ options, callback, name })
},
onTransform: (options, callback) => {
this.transforms.push({ options, callback, name })
},
onClose: (options, callback) => {
this.closers.push({ options, callback, name })
},
})
}
}
modulesToPrebundle() {
return flatten(this.plugins.map((p) => p.modulesToPrebundle || []))
}
private matches(
options: { filter: RegExp; namespace?: string },
arg: { path?: string; namespace?: string },
) {
if (!arg.path) {
return false
}
if (options.filter && !options.filter.test(arg.path)) {
return false
}
const optsNamespace = options.namespace || 'file'
const argNamespace = arg.namespace || 'file'
if (argNamespace !== optsNamespace) {
return false
}
return true
}
async load(arg: esbuild.OnLoadArgs): Promise<Maybe<esbuild.OnLoadResult>> {
let result
for (let { callback, options, name } of this.loaders) {
if (this.matches(options, arg)) {
try {
logger.debug(
`loading '${osAgnosticPath(
arg.path,
this.ctx.root,
)}' with '${name}'`,
)
const newResult = await callback(arg)
if (newResult) {
result = newResult
if (!result.pluginName) {
result.pluginName = name
}
break
}
} catch (e) {
if (e && e?.message) {
e.plugin = name
}
throw e
}
}
}
if (result) {
return { ...result, namespace: result.namespace || 'file' }
}
}
async transform(arg: OnTransformArgs): Promise<OnTransformResult> {
let result: OnTransformResult = { contents: arg.contents }
for (let { callback, options, name } of this.transforms) {
try {
if (this.matches(options, arg)) {
logger.debug(`transforming '${arg.path}' with '${name}'`)
const newResult = await callback(arg)
if (newResult?.contents != null) {
arg.contents = newResult.contents
result.contents = newResult.contents
}
if (newResult?.loader) {
arg.loader = newResult.loader
result.loader = newResult.loader
}
// merge with previous source maps
if (newResult?.map) {
if (result.map) {
result.map = mergeSourceMap(
result.map,
newResult.map,
)
} else {
result.map = newResult.map
}
}
}
} catch (e) {
if (e && e?.message) {
e.plugin = name
}
throw e
}
}
return result
}
/**
* Resolve filter should match on basename and not rely on absolute path, "virtual" could be passed as absolute paths from root: /path/to/virtual_file
*/
async resolve(
arg: Partial<esbuild.OnResolveArgs> & { skipOnResolved?: boolean },
): Promise<Maybe<esbuild.OnResolveResult>> {
let result
// support for resolving paths with queries
for (let { callback, options, name } of this.resolvers) {
if (this.matches(options, arg)) {
logger.debug(`resolving '${arg.path}' with '${name}'`)
const newResult = await callback({
importer: '',
namespace: 'file',
pluginData: undefined,
resolveDir: '',
path: '',
kind: 'import-statement', // TODO fix wrong kind in resolve
...arg,
})
if (newResult && newResult.path) {
logger.debug(
`resolved '${
arg.path
}' with '${name}' as '${osAgnosticPath(
newResult.path,
this.ctx.root,
)}'`,
)
result = newResult
if (!result.pluginName) {
result.pluginName = name
}
break
}
// break
}
}
if (result) {
result = { ...result, namespace: result.namespace || 'file' }
// register resolved modules that do not exist to real file paths, so that i can resolve them in onFileChange
if (this.ctx?.graph && arg.path && !fs.existsSync(result.path)) {
try {
const realPath = await resolveAsync(arg.path, {
basedir: arg.resolveDir || arg.importer,
mainFields: MAIN_FIELDS,
})
if (realPath) {
if (this.ctx.graph.realToFake[realPath]) {
this.ctx.graph.realToFake[realPath].add(result.path)
} else {
this.ctx.graph.realToFake[realPath] = new Set([
result.path,
])
}
}
} catch {}
}
if (!arg.skipOnResolved && this.onResolved) {
const newResult = await this.onResolved({
...result,
importer: arg.importer,
})
if (newResult) {
return newResult
}
}
return result
}
}
async close() {
let result
for (let { callback, options, name } of this.closers) {
logger.debug(`cleaning resources for '${name}'`)
await callback()
}
return result
}
async resolveLoadTransform({
path: p,
importer = '',
namespace = 'file',
expectedExtensions,
skipOnResolved,
}: {
path: string
importer?: string
namespace?: string
skipOnResolved?: boolean
expectedExtensions?: string[]
}): Promise<{ path?: string; contents?: string }> {
let resolveDir = path.dirname(p)
if (resolveDir === '/' || resolveDir === '.') {
resolveDir = ''
}
const resolved = await this.resolve({
importer,
namespace,
path: p,
resolveDir,
skipOnResolved,
})
if (resolved?.pluginData) {
logger.warn(
`pluginData is not supported by bundless, used by plugin ${resolved.pluginName}`,
)
}
if (!resolved || !resolved.path) {
return {}
}
if (
expectedExtensions &&
!expectedExtensions.includes(path.extname(resolved.path))
) {
return {}
}
const loaded = await this.load({
namespace: resolved.namespace || 'file',
path: resolved.path,
pluginData: undefined,
})
if (loaded?.pluginData) {
logger.warn(
`esbuild pluginData is not supported by bundless, used by plugin ${loaded.pluginName}`,
)
}
if (!loaded) {
return {}
}
const transformed = await this.transform({
contents: String(loaded.contents),
path: resolved.path,
loader: loaded.loader || 'default',
namespace: resolved.namespace || 'file',
})
if (!transformed) {
return { contents: String(loaded.contents), path: resolved.path }
}
return { contents: String(transformed.contents), path: resolved.path }
}
esbuildPlugins() {
return this.plugins.map((plugin, index) =>
this.wrapPluginForEsbuild(plugin),
)
}
profilingData: {
resolvers: Record<string, number>
loaders: Record<string, number>
transforms: Record<string, number>
} = {
resolvers: {},
loaders: {},
transforms: {},
}
printProfilingResult() {
let str = '\n\nProfiling data:\n\n'
// console.log(this.profilingData)
const data = Object.keys(this.profilingData).map((k) => {
const timeConsume: number = Object.values(
this.profilingData[k],
).reduce(sum, 0) as any
return {
path: k,
timeConsume,
}
})
if (data.map((x) => x.timeConsume).reduce(sum, 0) === 0) {
return ''
}
str += ansiChart(data)
str += '\n\nResolvers\n\n'
const resolversData = Object.keys(this.profilingData.resolvers).map(
(pluginName) => {
return {
path: pluginName,
timeConsume: this.profilingData.resolvers[pluginName],
}
},
)
const opts = { limit: 3 }
str += ansiChart(resolversData, opts)
str += '\n\nLoaders\n\n'
const loadersData = Object.keys(this.profilingData.loaders).map(
(pluginName) => {
return {
path: pluginName,
timeConsume: this.profilingData.loaders[pluginName],
}
},
)
str += ansiChart(loadersData, opts)
str += '\n\nTransforms\n\n'
const transformsData = Object.keys(this.profilingData.transforms).map(
(pluginName) => {
return {
path: pluginName,
timeConsume: this.profilingData.transforms[pluginName],
}
},
)
str += ansiChart(transformsData, opts)
str += '\n'
return str
}
private wrapPluginForProfiling(plugin: Plugin): Plugin {
const pluginsExecutor: PluginsExecutor = this
const { profilingData: profiledData } = this
const { name } = plugin
function wrapMethod(method, type: string) {
return async (...args) => {
const timeStart = Date.now()
const res = await method(...args)
const delta = Date.now() - timeStart
profiledData[type][name] =
(profiledData[type][name] || 0) + delta
return res
}
}
return {
name,
setup(hooks) {
plugin.setup({
...hooks,
pluginsExecutor,
// wrap onLoad to execute other plugins transforms
onLoad: wrapMethod(hooks.onLoad, 'loaders'),
onResolve: wrapMethod(hooks.onResolve, 'resolvers'),
onTransform: wrapMethod(hooks.onTransform, 'transforms'),
})
},
}
}
private wrapPluginForEsbuild(plugin: Plugin): esbuild.Plugin {
const pluginsExecutor: PluginsExecutor = this
const ctx = this.ctx
const executor = this
return {
name: plugin.name,
setup({ onLoad, onResolve }) {
// TODO running setup 2 times
plugin.setup({
onResolve,
// the plugin transform is already inside pluginsExecutor
onTransform() {},
onClose() {},
ctx,
pluginsExecutor,
initialOptions: executor.startingInitialOptions,
// wrap onLoad to execute other plugins transforms
onLoad(options, callback) {
onLoad(options, async (args) => {
const result = await callback(args)
if (!result) {
return
}
// run all transforms from other plugins
const transformed = await pluginsExecutor.transform(
{
path: args.path,
contents: String(result?.contents),
loader: result.loader || 'default',
},
)
if (!transformed) {
return result
}
return {
...result,
contents: transformed.contents,
loader: transformed.loader || result.loader,
resolveDir: result.resolveDir,
}
})
},
})
},
}
}
}
const sum = (a, b): number => a + b
export function sortPlugins(plugins?: Plugin[]): [Plugin[], Plugin[]] {
if (!plugins) {
return [[], []]
}
const [pre, post]: Plugin[][] = [[], []]
for (let plugin of plugins) {
if (plugin.enforce === 'pre') {
pre.push(plugin)
} else if (plugin.enforce === 'post') {
post.push(plugin)
} else {
pre.push(plugin)
}
}
return [pre, post]
}
================================================
FILE: bundless/src/prebundle/__snapshots__/prebundle.test.ts.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`traverseWithEsbuild 1`] = `
Array [
"index.html",
"main.js",
"../../node_modules/slash/index.js",
"../../node_modules/react/index.js",
"node_modules/preact/hooks/dist/hooks.module.js",
"../../node_modules/react-dom/index.js",
]
`;
================================================
FILE: bundless/src/prebundle/esbuild.ts
================================================
import * as esbuild from 'esbuild'
import { Metafile } from 'esbuild'
import fromEntries from 'fromentries'
import fs from 'fs-extra'
import path from 'path'
import slash from 'slash'
import tmpfile from 'tmpfile'
import { Config, Platform } from '../config'
import { osAgnosticPath } from '../utils'
import * as plugins from '../plugins'
import {
defaultImportableAssets as defaultImportableAssets,
defaultLoader,
isRunningWithYarnPnp,
JS_EXTENSIONS,
MAIN_FIELDS,
} from '../constants'
import { logger } from '../logger'
import { DependencyStatsOutput } from './stats'
import {
OptimizeAnalysisResult,
runFunctionOnPaths,
stripColon,
} from './support'
import { PluginsExecutor } from '../plugins-executor'
export const commonEsbuildOptions = (
config: Config = {},
): esbuild.BuildOptions => {
const omitHashes = process.env.BUNDLESS_CONSISTENT_HMR_GRAPH_HASH != null
return {
target: 'es2020',
entryNames: !omitHashes ? '[dir]/[name]-[hash]' : '[dir]/[name]',
chunkNames: 'chunks/[name]-[hash]',
minify: false,
minifyIdentifiers: false,
minifySyntax: false,
metafile: true,
minifyWhitespace: false,
mainFields: MAIN_FIELDS,
sourcemap: false,
bundle: true,
platform: 'browser',
format: 'esm',
write: true,
logLevel: 'error',
loader: {
'.js': 'jsx',
'.cjs': 'js',
// '.svg': 'dataurl', // TODO enable svg as data uri in development and in build
...defaultLoader,
...config.loader,
},
define: generateDefineObject({ config }),
}
}
export function generateDefineObject({
config = {} as Config,
platform = 'browser' as Platform,
isProd = false,
}) {
if (platform === 'node') {
return {
'process.browser': 'false',
...config.define, // TODO mock browser stuff like fetch? this allows me to target other platform like cloudflare workers ...
}
}
const noop = 'String'
const nodeEnv =
process.env.NODE_ENV || (isProd ? 'production' : 'development')
return {
'process.env.NODE_ENV': JSON.stringify(nodeEnv),
// ...generateEnvReplacements(config.env || {}),
'process.pid': '0',
// global: 'window',
__filename: '""',
__dirname: '""',
// TODO remove defines and use inject instead
// TODO use the process inject instead of define
// process: '{}',
global: 'window',
// 'process.env': '{}',
'process.browser': 'true',
'process.version': '""',
// 'process.argv': '[]',
// module: '{}',
// Buffer: noop,
// 'process.cwd': noop,
// 'process.chdir': noop,
clearImmediate: noop,
setImmediate: noop,
...config.define,
}
}
export const defaultResolvableExtensions = [
...JS_EXTENSIONS,
...defaultImportableAssets,
'.json',
'.css',
]
export async function bundleWithEsBuild({
entryPoints,
root,
dest: destLoc,
config,
...options
}) {
const { alias = {}, externalPackages = [], minify = false } = options
const tsconfigTempFile = tmpfile('.json')
await fs.promises.writeFile(tsconfigTempFile, makeTsConfig({ alias }))
// rimraf.sync(destLoc) // do not delete or on flight imports will return 404
const initialOptions: esbuild.BuildOptions = {
entryPoints,
...commonEsbuildOptions(config),
splitting: true, // needed to dedupe modules
external: externalPackages,
minify: Boolean(minify),
minifyIdentifiers: Boolean(minify),
minifySyntax: Boolean(minify),
minifyWhitespace: Boolean(minify),
mainFields: MAIN_FIELDS,
tsconfig: tsconfigTempFile,
sourcemap: 'inline',
bundle: true,
write: true,
outdir: destLoc,
metafile: true,
}
const executor = new PluginsExecutor({
initialOptions,
ctx: {
config: { root },
isBuild: true,
root,
},
plugins: [
...(config.plugins || []),
plugins.NodeGlobalsPolyfillPlugin({
buffer: true,
process: true,
define: initialOptions.define,
}),
plugins.NodeModulesPolyfillPlugin({
namespace: 'node-modules-polyfills',
}),
plugins.CssPlugin(),
plugins.NodeResolvePlugin({
name: 'prebundle-node-resolve',
mainFields: MAIN_FIELDS,
extensions: [
...defaultResolvableExtensions,
...(Object.keys(config.loader || {}) || []),
],
onNonResolved: (p, importer, e) => {
logger.debug(e.message + '\n' + e.stack)
// logger.warn(
// `Cannot resolve '${p}' from '${importer}' during traversal, using yarn pnp: ${isRunningWithYarnPnp}`,
// )
},
}),
plugins.UrlResolverPlugin(),
],
})
const buildResult = await esbuild.build({
...initialOptions,
plugins: executor.esbuildPlugins(),
})
await fs.promises.unlink(tsconfigTempFile)
let meta = buildResult.metafile!
meta = runFunctionOnPaths(meta, (p) => {
p = stripColon(p) // namespace:/path/to/file -> /path/to/file
return p
})
const esbuildCwd = process.cwd()
const bundleMap = metafileToBundleMap({
meta,
esbuildCwd,
root,
})
const analysis = metafileToAnalysis({ meta, root, esbuildCwd })
const stats = metafileToStats({ meta, destLoc })
return { stats, bundleMap, analysis }
}
function makeTsConfig({ alias }) {
const aliases = Object.keys(alias || {}).map((k) => {
return {
[k]: [alias[k]],
}
})
const tsconfig = {
compilerOptions: { baseUrl: '.', paths: Object.assign({}, ...aliases) },
}
return JSON.stringify(tsconfig)
}
export type BundleMap = Partial<Record<string, string>>
/**
* Returns aon object that maps from entry (relative path from root) to output (relative path from root too)
*/
export function metafileToBundleMap(_options: {
root: string
esbuildCwd: string
meta: Metafile
}): BundleMap {
const { meta, root, esbuildCwd } = _options
const maps: Array<[string, string]> = Object.keys(meta.outputs)
.map((output): [string, string] | undefined => {
// chunks cannot be entrypoints
const entry = meta.outputs[output].entryPoint
if (!entry) {
return
}
return [
osAgnosticPath(path.resolve(esbuildCwd, entry), root),
osAgnosticPath(path.resolve(esbuildCwd, output), root),
]
})
.filter(Boolean) as any
const bundleMap = fromEntries(maps)
return bundleMap
}
function metafileToAnalysis(_options: {
meta: Metafile
root: string
esbuildCwd: string
}): OptimizeAnalysisResult {
const { meta, root, esbuildCwd } = _options
const analysis: OptimizeAnalysisResult = {
isCommonjs: fromEntries(
Object.keys(meta.outputs)
.map((output): [string, true] | undefined => {
if (path.basename(output).startsWith('chunk.')) {
return
}
const info = meta.outputs[output]
if (!info) {
throw new Error(`cannot find output info for ${output}`)
}
const isCommonjs =
info.exports?.length === 1 &&
info.exports?.[0] === 'default'
if (!isCommonjs) {
return
}
// what if imported path ahs not yet been converted by prebundler? then prebundler should lock server, it's impossible
return [
osAgnosticPath(path.resolve(esbuildCwd, output), root),
isCommonjs,
]
})
.filter(Boolean) as any,
),
}
return analysis
}
export function metafileToStats(_options: {
meta: Metafile
destLoc: string
}): DependencyStatsOutput {
const { meta, destLoc } = _options
const stats = Object.keys(meta.outputs).map((output) => {
const value = meta.outputs[output]
// const inputs = meta.outputs[output].bytes;
return {
path: output,
isCommon: ['chunk.'].some((x) =>
path.basename(output).startsWith(x),
),
bytes: value.bytes,
}
})
function makeStatObject(value) {
const relativePath = slash(path.relative(destLoc, value.path))
return {
[relativePath]: {
size: value.bytes,
// gzip: zlib.gzipSync(contents).byteLength,
// brotli: zlib.brotliCompressSync ? zlib.brotliCompressSync(contents).byteLength : 0,
},
}
}
return {
common: Object.assign(
{},
...stats.filter((x) => x.isCommon).map(makeStatObject),
),
direct: Object.assign(
{},
...stats.filter((x) => !x.isCommon).map(makeStatObject),
),
}
}
================================================
FILE: bundless/src/prebundle/index.ts
================================================
export { prebundle } from './prebundle'
================================================
FILE: bundless/src/prebundle/prebundle.test.ts
================================================
import memoize from 'micro-memoize'
import path from 'path'
import { makeEntryObject } from './prebundle'
import { traverseWithEsbuild } from './traverse'
test('traverseWithEsbuild', async () => {
const entry = path.resolve('fixtures/with-many-dependencies/index.html')
const deps = await traverseWithEsbuild({
entryPoints: [entry],
// esbuildCwd: process.cwd(),
config: {},
root: path.dirname(entry),
})
expect(deps).toMatchSnapshot()
})
test('memoize', () => {
let i = 0
const fn = memoize((x) => {
return i++
})
fn(1)
fn(1)
fn.cache.keys = []
fn.cache.values = []
fn(1)
fn(1)
fn(1)
expect(i).toBe(2)
})
test('makeEntryObject', () => {
const deps = ['xxx', 'xxx', 'xxx', 'yyy', 'aaa']
const obj = makeEntryObject(deps)
console.log(obj)
expect(Object.keys(obj).length).toBe(deps.length)
})
================================================
FILE: bundless/src/prebundle/prebundle.ts
================================================
import fs from 'fs-extra'
import path from 'path'
import chalk from 'chalk'
import {
BUNDLE_MAP_PATH,
COMMONJS_ANALYSIS_PATH,
pnpapi,
WEB_MODULES_PATH,
} from '../constants'
import { logger } from '../logger'
import { clearCommonjsAnalysisCache } from '../plugins/rewrite/commonjs'
import { bundleWithEsBuild, generateDefineObject } from './esbuild'
import { printStats } from './stats'
import { isEmpty, needsPrebundle, osAgnosticPath } from '../utils'
import { traverseWithEsbuild } from './traverse'
export async function prebundle({ entryPoints, config, root, dest }) {
try {
logger.spinStart(`Prebundling modules in '${WEB_MODULES_PATH}'`)
const traversalResult = await traverseWithEsbuild({
entryPoints,
root,
config,
filter: /^[\w@][^:]/, // bare name imports (no relative imports)
})
logger.debug(`traversed files`)
const dependenciesPaths = traversalResult.filter((p) =>
needsPrebundle(config, p),
)
await fs.remove(dest)
if (!dependenciesPaths.length) {
logger.log(`No dependencies to prebundle found`)
return {}
}
logger.log(
`Prebundling \n ${dependenciesPaths
.map((x) => getClearDependencyPath(x))
.map((x) => (path.isAbsolute(x) ? osAgnosticPath(x, root) : x))
.map((x) => chalk.cyanBright(x))
.join('\n ')}\n`,
)
// TODO separate build for workspaces and dependencies, build workspaces in watch mode, also pass user plugins
// TODO do not stop traversal on workspaces, grab all dependencies including inside workspaces (to node duplicate deps)
// TODO build workspaces in separate build step, make external dependencies using the needsPrebundle logic
let { bundleMap, analysis, stats } = await bundleWithEsBuild({
dest,
root,
config,
entryPoints: makeEntryObject(
dependenciesPaths.map((x) => path.resolve(root, x)),
),
})
logger.spinSucceed('\nFinish')
const analysisFile = path.resolve(root, COMMONJS_ANALYSIS_PATH)
await fs.createFile(analysisFile)
await fs.writeFile(analysisFile, JSON.stringify(analysis, null, 4))
console.info(
printStats({ dependencyStats: stats, destLoc: WEB_MODULES_PATH }),
)
if (!isEmpty(bundleMap)) {
const bundleMapCachePath = path.resolve(root, BUNDLE_MAP_PATH)
await fs.writeJSON(bundleMapCachePath, bundleMap, { spaces: 4 })
}
return bundleMap
} catch (e) {
logger.spinFail('Cannot prebundle\n')
throw e
} finally {
clearCommonjsAnalysisCache()
}
}
function getClearDependencyPath(p: string) {
const index = p.lastIndexOf('node_modules')
if (index === -1) {
return p
}
let dependencySubPath = p.slice(index).replace(/\/?node_modules(\/|\\)/, '')
return dependencySubPath
}
function getScopedPackageName(path: string): any {
return path.match(/(@[\w-_\.]+\/[\w-_\.]+)/)?.[1] || ''
}
function getPackageName(p: string) {
const dependencySubPath = getClearDependencyPath(p)
let dependency = ''
if (dependencySubPath.startsWith('@')) {
dependency = getScopedPackageName(dependencySubPath) || ''
} else {
const lastIndex = dependencySubPath.indexOf('/')
dependency = dependencySubPath.slice(
0,
lastIndex === -1 ? undefined : lastIndex,
)
}
return dependency
}
export function makeEntryObject(dependenciesPaths: string[]) {
const names: Record<string, number> = {}
return Object.assign(
{},
...dependenciesPaths.map((f) => {
let outputPath = getClearDependencyPath(f) || 'unknown'
const sameNamesCount = names[outputPath]
if (sameNamesCount) {
names[outputPath] += 1
outputPath += String(sameNamesCount)
} else {
names[outputPath] = 1
}
return {
[outputPath]: f,
}
}),
)
}
================================================
FILE: bundless/src/prebundle/stats.ts
================================================
import chalk from 'chalk'
export type DependencyType = 'direct' | 'common'
export type DependencyStatsMap = {
[filePath: string]: DependencyStats
}
type DependencyStats = { size: number }
export type DependencyStatsOutput = Record<DependencyType, DependencyStatsMap>
export function printStats(_args: {
dependencyStats: DependencyStatsOutput
destLoc: string
}): string {
const { dependencyStats, destLoc } = _args
let output = ''
const { direct, common } = dependencyStats
const allDirect = Object.entries(direct).sort(entriesSort)
const allCommon = Object.entries(common).sort(entriesSort)
const maxFileNameLength =
[...allCommon, ...allDirect].reduce(
(max, [filename]) => Math.max(filename.length, max),
destLoc.length,
) + 1
output +=
` ⦿ ${chalk.bold(destLoc.padEnd(maxFileNameLength + 4))}` +
chalk.bold(chalk.underline('size'.padEnd(SIZE_COLUMN_WIDTH - 2))) +
' ' +
// chalk.bold(chalk.underline('gzip'.padEnd(SIZE_COLUMN_WIDTH - 2))) +
// ' ' +
// chalk.bold(chalk.underline('brotli'.padEnd(SIZE_COLUMN_WIDTH - 2))) +
`\n`
output += `${formatFiles(allDirect, maxFileNameLength)}\n`
if (Object.values(common).length > 0) {
output += ` ⦿ ${chalk.bold('chunks (Shared)')}\n`
output += `${formatFiles(allCommon, maxFileNameLength)}`
}
return `\n${output}\n`
}
/** The minimum width, in characters, of each size column */
const SIZE_COLUMN_WIDTH = 11
/** Generic Object.entries() alphabetical sort by keys. */
function entriesSort([filenameA]: [string, any], [filenameB]: [string, any]) {
return filenameA.localeCompare(filenameB)
}
/** Pretty-prints number of bytes as "XXX KB" */
function formatSize(size) {
let kb = Math.round((size / 1000) * 100) / 100
if (kb >= 1000) {
kb = Math.floor(kb)
}
let color
if (kb < 15) {
color = 'green'
} else if (kb < 30) {
color = 'yellow'
} else {
color = 'red'
}
return chalk[color](`${kb} KB`.padEnd(SIZE_COLUMN_WIDTH))
}
function formatDelta(delta) {
const kb = Math.round(delta * 100) / 100
const color = delta > 0 ? 'red' : 'green'
return chalk[color](`Δ ${delta > 0 ? '+' : ''}${kb} KB`)
}
function formatFileInfo(
filename: string,
stats: DependencyStats,
padEnd: number,
isLastFile: boolean,
): string {
const lineGlyph = chalk.dim(isLastFile ? '└─' : '├─')
const lineName = filename.padEnd(padEnd)
const fileStat = formatSize(stats.size)
// const gzipStat = formatSize(stats.gzip)
// const brotliStat = formatSize(stats.brotli)
const lineStat = fileStat // + gzipStat + brotliStat
let lineDelta = ''
// if (stats.delta) {
// lineDelta = chalk.dim('[') + formatDelta(stats.delta) + chalk.dim(']')
// }
// Trim trailing whitespace (can mess with formatting), but keep indentation.
return ` ` + `${lineGlyph} ${lineName} ${lineStat} ${lineDelta}`.trim()
}
function formatFiles(files: [string, DependencyStats][], padEnd: number) {
const strippedFiles = files.map(([filename, stats]) => [
filename.replace(/^common\//, ''),
stats,
]) as [string, DependencyStats][]
return strippedFiles
.map(([filename, stats], index) =>
formatFileInfo(filename, stats, padEnd, index >= files.length - 1),
)
.join('\n')
}
================================================
FILE: bundless/src/prebundle/support.ts
================================================
import { Metafile } from 'esbuild'
import { forOwn, isPlainObject } from 'lodash'
export function isUrl(req: string) {
return (
req.startsWith('http://') ||
req.startsWith('https://') ||
req.startsWith('//')
)
}
export interface OptimizeAnalysisResult {
isCommonjs: { [name: string]: true }
}
export function unique<T>(array: T[], key = (x: T): any => x): T[] {
const cache: Record<any, boolean> = {}
return array.filter(function (a) {
const keyed = key(a)
if (!cache[keyed]) {
cache[keyed] = true
return true
}
return false
}, {})
}
// namespace:/path/to/file -> /path/to/file
export function stripColon(input?: string) {
if (!input) {
return ''
}
const index = input.indexOf(':')
if (index === -1) {
return input
}
const clean = input.slice(index + 1)
return clean
}
function convertKeys<T>(obj: T, cb: (k: string) => string): T {
const x: T = Array.isArray(obj) ? ([] as any) : {}
forOwn(obj, (v, k) => {
if (isPlainObject(v) || Array.isArray(v)) v = convertKeys(v, cb)
x[cb(k)] = v
})
return x
}
export function runFunctionOnPaths(
x: Metafile,
func: (x: string) => string = stripColon,
): Metafile {
x = convertKeys(x, func)
for (const input in x.inputs) {
const v = x.inputs[input]
x.inputs[input] = {
...v,
imports: v.imports
? v.imports.map((x) => ({ ...x, path: func(x.path) }))
: [],
}
}
for (const output in x.outputs) {
const v = x.outputs[output]
x.outputs[output] = {
...v,
imports: v.imports
? v.imports.map((x) => ({ ...x, path: func(x.path) }))
: [],
}
}
return x
}
================================================
FILE: bundless/src/prebundle/traverse.ts
================================================
import deepmerge from 'deepmerge'
import * as esbuild from 'esbuild'
import { build, BuildOptions, Metafile, Plugin } from 'esbuild'
import fromEntries from 'fromentries'
import { promises as fsp } from 'fs'
import { resolveAsync } from '@esbuild-plugins/all'
import fsx from 'fs-extra'
import os from 'os'
import path from 'path'
import { isRunningWithYarnPnp, MAIN_FIELDS } from '../constants'
import { HmrGraph } from '../hmr-graph'
import { logger } from '../logger'
import { PluginsExecutor } from '../plugins-executor'
import * as plugins from '../plugins'
import { flatten, needsPrebundle, osAgnosticPath } from '../utils'
import {
commonEsbuildOptions,
generateDefineObject,
defaultResolvableExtensions,
} from './esbuild'
import { runFunctionOnPaths, stripColon, unique } from './support'
import { rewriteScriptUrlsTransform } from '../serve'
import { Config } from '../config'
type Args = {
root: string
entryPoints: string[]
config: Config
filter?: RegExp
esbuildOptions?: Partial<BuildOptions>
// resolver?: (cwd: string, id: string) => string
stopTraversing?: (resolvedPath: string) => boolean
}
export async function traverseWithEsbuild({
entryPoints,
filter,
root,
config,
}: Args): Promise<string[]> {
const userPlugins = config.plugins || []
const destLoc = await fsp.realpath(
path.resolve(await fsp.mkdtemp(path.join(os.tmpdir(), 'dest'))),
)
for (let entry of entryPoints) {
if (!path.isAbsolute(entry)) {
throw new Error(
`All entryPoints of traverseWithEsbuild must be absolute: ${entry}`,
)
}
}
logger.debug(`Traversing entrypoints ${JSON.stringify(entryPoints, [], 4)}`)
const allPlugins = [
// TODO esbuild does not let overriding plugins, this means that if user is using plugin to alias a package to a file it will skip ExternalButInMetafile and break everything
...(userPlugins || []),
plugins.NodeModulesPolyfillPlugin(),
plugins.HtmlResolverPlugin(),
plugins.HtmlTransformUrlsPlugin({
transforms: [rewriteScriptUrlsTransform],
}),
plugins.HtmlIngestPlugin({ root }),
plugins.NodeResolvePlugin({
name: 'traverse-node-resolve',
mainFields: MAIN_FIELDS,
extensions: [
...defaultResolvableExtensions,
...(Object.keys(config.loader || {}) || []),
],
// TODO use different plugin that only runs on bare imports
onNonResolved: (p, importer, e) => {
logger.debug(e.message + '\n' + e.stack)
// logger.warn(
// `Cannot resolve '${p}' from '${importer}' during traversal, using yarn pnp: ${isRunningWithYarnPnp}`,
// )
},
}),
plugins.UrlResolverPlugin(),
]
const initialOptions: esbuild.BuildOptions = {
...commonEsbuildOptions(config),
entryPoints,
outdir: destLoc,
}
const pluginsExecutor = new PluginsExecutor({
plugins: allPlugins,
initialOptions,
ctx: {
isBuild: true,
config: { root },
root,
},
})
let graph: TraversalGraph = {}
try {
await build({
...initialOptions,
plugins: [
traversalGraphPlugin({
executor: pluginsExecutor,
graph,
filter,
stopTraversing(p) {
return needsPrebundle(config, p)
},
}),
...pluginsExecutor.esbuildPlugins(),
],
})
// console.log(JSON.stringify(meta, null, 4))
let knownModules = pluginsExecutor.modulesToPrebundle()
knownModules = await Promise.all(
knownModules.map((x) =>
resolveAsync(x, {
basedir: root,
mainFields: MAIN_FIELDS,
}).then((x) => x || ''),
),
)
knownModules = knownModules.filter(Boolean)
return unique([...Object.keys(graph), ...knownModules])
} finally {
await fsx.remove(destLoc)
}
}
export function traversalGraphPlugin({
filter,
graph,
executor,
stopTraversing,
}: {
filter?: RegExp
graph: TraversalGraph
executor: PluginsExecutor
stopTraversing: Function
}): esbuild.Plugin {
return {
name: 'register-modules',
setup({ onResolve }) {
onResolve({ filter: filter || /()/ }, async (args) => {
const res = await executor.resolve({
importer: args.importer,
path: args.path,
namespace: 'file',
resolveDir: args.importer
? path.dirname(args.importer)
: args.resolveDir,
skipOnResolved: true,
})
if (!res || !res.path) {
return res
}
const importer = osAgnosticPath(
args.importer,
executor.ctx.root,
)
const importee = osAgnosticPath(res.path, executor.ctx.root)
if (importer) {
if (!graph[importer]) {
graph[importer] = [importee]
} else {
graph[importer].push(importee)
}
}
if (!graph[importee]) {
graph[importee] = []
}
if (stopTraversing(res.path)) {
logger.debug(
`Stopping traversing at ${res.path}, ${args.path}`,
)
return { external: true }
}
})
},
}
}
type TraversalGraph = Record<string, string[]>
/**
* Returns a module graph implemented as an object, keys are modules (relative paths from root), values are arrays of key's imports (absolute paths)
*/
export function metaToTraversalResult({
meta,
entryPoints,
esbuildCwd,
root,
}: {
meta: Metafile
esbuildCwd: string
root: string
entryPoints: string[]
}): TraversalGraph {
if (!path.isAbsolute(esbuildCwd)) {
throw new Error('esbuildCwd must be an absolute path')
}
for (let entry of entryPoints) {
if (!path.isAbsolute(entry)) {
throw new Error('entry must be an absolute path')
}
}
const alreadyProcessed = new Set<string>()
// must be all absolute paths
let toProcess = entryPoints
const result: TraversalGraph = {}
// abs path -> input info
const inputs: Record<string, { imports: { path: string }[] }> = fromEntries(
Object.keys(meta.inputs).map((k) => {
const abs = path.resolve(esbuildCwd, k)
return [abs, meta.inputs[k]]
}),
)
while (toProcess.length) {
const newImports = flatten(
toProcess.map((absPath): string[] => {
if (alreadyProcessed.has(absPath)) {
return []
}
alreadyProcessed.add(absPath)
// newEntry = path.posix.normalize(newEntry) // TODO does esbuild always use posix?
const input = inputs[absPath]
if (input == null) {
throw new Error(
`entry '${absPath}' is not present in esbuild metafile inputs ${JSON.stringify(
Object.keys(inputs),
null,
2,
)}`,
)
}
// abs paths
const currentImports: string[] = input.imports
? input.imports
.map((x) => x.path)
.map((x) => {
if (!path.isAbsolute(x)) {
return path.resolve(esbuildCwd, x)
}
return x
})
.filter((x) => Boolean(x))
: []
// newImports.push(...currentImports)
const importer = osAgnosticPath(
path.resolve(esbuildCwd, absPath),
root,
)
if (!result[importer]) {
result[importer] = []
}
for (let importee of currentImports) {
if (!importee) {
continue
}
importee = osAgnosticPath(importee, root)
result[importer].push(importee)
}
return currentImports
}),
).filter(Boolean)
toProcess = newImports
}
return result
// find the right output getting the key of the right output.inputs == input
// get the imports of the inputs.[entry].imports and attach them the importer
// do the same with the imports just found
// return the list of input files
}
================================================
FILE: bundless/src/serve.ts
================================================
import chalk from 'chalk'
import chokidar, { FSWatcher } from 'chokidar'
import { createHash } from 'crypto'
import * as esbuild from 'esbuild'
import findUp from 'find-up'
import fs from 'fs-extra'
import { getPort } from 'get-port-please'
import { Server } from 'http'
import Koa, { DefaultContext, DefaultState } from 'koa'
import etagMiddleware from 'koa-etag'
import net from 'net'
import path from 'path'
import { Node } from 'posthtml'
import slash from 'slash'
import { promisify } from 'util'
import { HMRPayload } from './client/types'
import { Config, defaultConfig, getEntries, normalizeConfig } from './config'
import {
BUNDLE_MAP_PATH,
DEFAULT_PORT,
defaultImportableAssets,
JS_EXTENSIONS,
MAIN_FIELDS,
showGraph,
WEB_MODULES_PATH,
pnpapi,
} from './constants'
import { HmrGraph } from './hmr-graph'
import { logger } from './logger'
import * as middlewares from './middleware'
import * as plugins from './plugins'
import {
OnResolved,
PluginsExecutor,
PluginsExecutorCtx,
sortPlugins,
} from './plugins-executor'
import { prebundle } from './prebundle'
import { BundleMap, generateDefineObject } from './prebundle/esbuild'
import { isUrl } from './prebundle/support'
import {
appendQuery,
isEmpty,
Lock,
needsPrebundle,
osAgnosticPath,
parseWithQuery,
prepareError,
} from './utils'
process.env.NODE_ENV = process.env.NODE_ENV || 'development'
export interface ServerPluginContext {
root: string
app: Koa
graph: HmrGraph
pluginExecutor: PluginsExecutor
// server: Server
watcher: FSWatcher
server?: Server
config: Config
sendHmrMessage: (payload: HMRPayload) => void
port: number
}
export type ServerMiddleware = (ctx: ServerPluginContext) => void
export async function serve(config: Config) {
config = normalizeConfig(config)
let server = new Server()
const { app } = await createDevApp(server, config)
server.on('request', app.callback())
const preferredServerPort = config.server?.port || DEFAULT_PORT
const port = await getPort(preferredServerPort)
if (Number(preferredServerPort) !== Number(port)) {
logger.warn(
`Using port ${port} because ${preferredServerPort} is already in use`,
)
}
await promisify(server.listen.bind(server) as any)(port)
process.stdout.write('\n')
logger.log(
`Listening on ${chalk.cyan.underline(`http://localhost:${port}`)}`,
)
return server
}
export const onResolveLock = new Lock()
export async function createDevApp(server: net.Server, config: Config) {
config = normalizeConfig(config)
if (!config.root) {
config.root = process.cwd()
}
const { root } = config
const app = new Koa<DefaultState, DefaultContext>()
const graph = new HmrGraph({ root, server })
const watcher = chokidar.watch(root, {
ignored: ['**/node_modules/**', '**/.git/**', '**/.bundless'],
useFsEvents: shouldUseFsEvents(),
ignoreInitial: true,
// ...chokidarWatchOptions
})
const executorCtx: PluginsExecutorCtx = {
config,
isBuild: false,
graph,
root,
watcher,
}
// when resolving if we encounter a node_module run the prebundling phase and invalidate some caches
const onResolved: OnResolved = async function onResolved(arg) {
const { path: resolvedPath, importer } = arg
if (!resolvedPath) {
return
}
try {
// lock browser requests until not prebundled
await onResolveLock.wait()
if (!needsPrebundle(config, resolvedPath)) {
return
}
let relativePath = osAgnosticPath(resolvedPath, root)
if (bundleMap && bundleMap[relativePath]) {
const webBundle = bundleMap[relativePath]
return { ...arg, path: path.resolve(root, webBundle!) }
}
onResolveLock.lock()
// TODO do not rerun prebundle if file extension is an asset like css?
logger.log(
`Found still not bundled module '${relativePath}' imported by '${importer}', running prebundle phase:`,
)
logger.debug(resolvedPath)
graph.sendHmrMessage({
type: 'overlay-info-open',
info: {
message: `Prebundling dependencies, please wait`,
showSpinner: true,
},
})
// node module path not bundled, rerun bundling
const entryPoints = await getEntries(pluginsExecutor, config)
logger.debug(`got entries`)
// TODO make prebundled files cachable indefinitley given they are named with an hash
bundleMap = await prebundle({
entryPoints,
dest: path.resolve(root, WEB_MODULES_PATH),
config,
root,
}).catch((e) => {
graph.sendHmrMessage({
type: 'overlay-info-close',
})
graph.sendHmrMessage({
type: 'overlay-error',
err: prepareError(e),
})
throw e
})
graph.sendHmrMessage({
type: 'overlay-info-close',
})
await updateHash(hashPath, depsHash)
graph.sendHmrMessage({ type: 'reload' })
const webBundle = bundleMap[relativePath]
if (!webBundle) {
throw new Error(
`Bundle for '${relativePath}' was not generated in prebundling phase`,
)
}
return { ...arg, path: path.resolve(root, webBundle) }
} catch (e) {
throw e
} finally {
onResolveLock.ready()
}
}
const [prePlugins, postPlugins] = sortPlugins(config.plugins)
const initialOptions: esbuild.BuildOptions = {
loader: config.loader,
bundle: false,
minify: false,
define: config.define,
} // TODO better esbuild initialOptions for serve
// most of the logic is in plugins
const pluginsExecutor = new PluginsExecutor({
ctx: executorCtx,
isProfiling: config.printStats,
initialOptions,
onResolved,
plugins: [
...prePlugins,
// TODO resolve `data:` imports, rollup emits imports with data: ...
plugins.HtmlResolverPlugin(),
plugins.UrlResolverPlugin(), // resolves urls with queries
plugins.HmrClientPlugin({
getPort: () => server.address()?.['port'],
}),
plugins.CssPlugin(),
// NodeResolvePlugin must be called first, to not skip prebundling
plugins.NodeResolvePlugin({
name: 'node-resolve',
mainFields: MAIN_FIELDS,
extensions: [...JS_EXTENSIONS],
}),
plugins.AssetsPlugin({
loader: config.loader,
}),
plugins.NodeModulesPolyfillPlugin({ namespace: 'node-builtins' }),
plugins.EsbuildTransformPlugin(),
plugins.JSONPlugin(),
plugins.ResolveSourcemapPlugin(),
plugins.HtmlTransformUrlsPlugin({
// must come before rewrite to not warn about the client script not having type=module
transforms: [rewriteScriptUrlsTransform],
}),
plugins.SourceMapSupportPlugin(), // adds source map to errors traces, must be after hmr client plugin
...postPlugins,
plugins.RewritePlugin(),
],
})
const bundleMapCachePath = path.resolve(root, BUNDLE_MAP_PATH)
const hashPath = path.resolve(root, WEB_MODULES_PATH, 'deps_hash')
const depsHash = await getDepsHash(root)
let prevHash = await fs
.readFile(hashPath)
.catch(() => '')
.then((x) => x.toString().trim())
const isHashDifferent = !depsHash || !prevHash || prevHash !== depsHash
if (config.prebundle?.force || isHashDifferent) {
if (isHashDifferent) {
logger.log(`Dependencies changed, running prebundle phase`)
logger.debug('isHashDifferent', isHashDifferent, prevHash, depsHash)
}
await fs.remove(path.resolve(root, '.bundless'))
}
let bundleMap: BundleMap = await fs
.readJSON(bundleMapCachePath)
.catch(() => {
return {}
})
if (isEmpty(bundleMap)) {
bundleMap = await prebundle({
entryPoints: await getEntries(pluginsExecutor, config),
config,
dest: path.resolve(root, WEB_MODULES_PATH),
root,
})
await updateHash(hashPath, depsHash)
}
server.once('close', async () => {
logger.debug('closing')
await Promise.all([watcher.close(), pluginsExecutor.close()])
app.emit('closed')
})
if (config.printStats) {
process.on('SIGINT', () => {
process.stdout.write('\n')
console.info(pluginsExecutor.printProfilingResult())
process.exit(0)
})
}
app.on('error', (e: Error) => {
console.error(chalk.red(e.message))
console.error(chalk.red(e.stack))
graph.sendHmrMessage({ type: 'overlay-error', err: prepareError(e) })
})
server.once('listening', () => {
config.server = { ...config.server, port: server.address()?.['port'] }
})
if (config.server?.hmr) {
watcher.on('change', (filePath) => {
graph.onFileChange({
filePath,
})
if (showGraph) {
logger.log(graph.toString())
}
})
}
// only js ends up here
app.use(middlewares.openInEditorMiddleware({ root }))
app.use(middlewares.sourcemapMiddleware({ root }))
app.use(middlewares.pluginsMiddleware({ root, pluginsExecutor, watcher }))
app.use(middlewares.historyFallbackMiddleware({ root, pluginsExecutor }))
app.use(middlewares.staticServeMiddleware({ root }))
app.use(
middlewares.staticServeMiddleware({
root: path.resolve(root, 'public'),
}),
)
// app.use(etagMiddleware())
// cors
if (config.server?.cors) {
app.use(
require('@koa/cors')(
typeof config.server?.cors === 'boolean'
? {}
: config.server?.cors,
),
)
}
return { app, pluginsExecutor }
}
// hash assumes that import paths can only grow when installed dependencies grow, this is not the case for deep paths like `lodash/path`, in these cases you will need to use `--force`
// TODO include config in hash
async function getDepsHash(root: string) {
const lockfileLoc = await findUp(
['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml'],
{
cwd: root,
},
)
if (!lockfileLoc) {
return ''
}
const content = await (await fs.readFile(lockfileLoc, 'utf-8')).toString()
return createHash('sha1').update(content).digest('base64').trim()
}
async function updateHash(hashPath: string, newHash: string) {
await fs.createFile(hashPath)
await fs.writeFile(hashPath, newHash.trim())
}
export const rewriteScriptUrlsTransform = (tree: Node) => {
let count = 0
tree.walk((node) => {
if (
node &&
node.tag === 'script' &&
node.attrs &&
node.attrs['src'] &&
!isUrl(node.attrs['src'])
) {
count += 1
let importPath = node.attrs['src']
if (node.attrs['type'] !== 'module') {
logger.warn(
`<script src="${importPath}"> is missing type="module". Only module scripts are processed by Bundless`,
)
}
const { query } = parseWithQuery(importPath)
if (query?.namespace != null) {
return node
}
node.attrs['src'] = appendQuery(importPath, `namespace=file`)
}
return node as any
})
}
function shouldUseFsEvents() {
try {
eval('require')('fsevents')
return true
} catch (e) {}
return false
}
================================================
FILE: bundless/src/utils/index.ts
================================================
export * from './sourcemaps'
export * from './path'
export * from './utils'
================================================
FILE: bundless/src/utils/path.test.ts
================================================
import os from 'os'
import path from 'path'
import { fileToImportPath, importPathToFile } from './path'
const root = path.resolve(__dirname)
describe('fileToImportPath and importPathToFile', () => {
const cases: {
path: string
expected: string
onlyWin?: boolean
onlyUnix?: true
}[] = [
{ path: '../cosa/index.ts', expected: '/__..__/cosa/index.ts' },
{
path: '../cosa/folder/index.ts',
expected: '/__..__/cosa/folder/index.ts',
gitextract_cw0f3mmf/
├── .changeset/
│ ├── README.md
│ └── config.json
├── .github/
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── .prettierrc
├── .vscode/
│ └── settings.json
├── README.md
├── TODOS.md
├── bundless/
│ ├── CHANGELOG.md
│ ├── bin.js
│ ├── package.json
│ ├── src/
│ │ ├── build/
│ │ │ └── index.ts
│ │ ├── cli.ts
│ │ ├── client/
│ │ │ ├── template.ts
│ │ │ └── types.ts
│ │ ├── config.ts
│ │ ├── constants.ts
│ │ ├── hmr-graph.ts
│ │ ├── index.ts
│ │ ├── logger.ts
│ │ ├── middleware/
│ │ │ ├── history-fallback.ts
│ │ │ ├── index.ts
│ │ │ ├── open-in-editor.ts
│ │ │ ├── plugins.ts
│ │ │ ├── sourcemap.ts
│ │ │ └── static-serve.ts
│ │ ├── plugins/
│ │ │ ├── assets.ts
│ │ │ ├── buffer.ts
│ │ │ ├── css.ts
│ │ │ ├── env.ts
│ │ │ ├── esbuild.ts
│ │ │ ├── hmr-client.ts
│ │ │ ├── html-ingest.ts
│ │ │ ├── html-resolver.ts
│ │ │ ├── html-transform.ts
│ │ │ ├── index.ts
│ │ │ ├── json.ts
│ │ │ ├── resolve-sourcemaps.ts
│ │ │ ├── rewrite/
│ │ │ │ ├── __snapshots__/
│ │ │ │ │ └── commonjs.test.ts.snap
│ │ │ │ ├── commonjs.test.ts
│ │ │ │ ├── commonjs.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── rewrite.ts
│ │ │ ├── source-map-support.ts
│ │ │ └── url-resolver.ts
│ │ ├── plugins-executor.ts
│ │ ├── prebundle/
│ │ │ ├── __snapshots__/
│ │ │ │ └── prebundle.test.ts.snap
│ │ │ ├── esbuild.ts
│ │ │ ├── index.ts
│ │ │ ├── prebundle.test.ts
│ │ │ ├── prebundle.ts
│ │ │ ├── stats.ts
│ │ │ ├── support.ts
│ │ │ └── traverse.ts
│ │ ├── serve.ts
│ │ └── utils/
│ │ ├── index.ts
│ │ ├── path.test.ts
│ │ ├── path.ts
│ │ ├── profiling.test.ts
│ │ ├── profiling.ts
│ │ ├── sourcemaps.ts
│ │ └── utils.ts
│ └── tsconfig.json
├── examples/
│ ├── react-javascript/
│ │ ├── .gitignore
│ │ ├── bundless.config.js
│ │ ├── package.json
│ │ ├── public/
│ │ │ └── index.html
│ │ └── src/
│ │ ├── app.jsx
│ │ ├── index.jsx
│ │ └── styles.css
│ ├── react-typescript/
│ │ ├── .gitignore
│ │ ├── bundless.config.js
│ │ ├── package.json
│ │ ├── public/
│ │ │ └── index.html
│ │ ├── src/
│ │ │ ├── app.tsx
│ │ │ ├── index.tsx
│ │ │ └── styles.css
│ │ └── tsconfig.json
│ ├── svelte/
│ │ ├── .gitignore
│ │ ├── bundless.config.js
│ │ ├── package.json
│ │ ├── public/
│ │ │ └── index.html
│ │ ├── scripts/
│ │ │ └── setupTypeScript.js
│ │ └── src/
│ │ ├── App.svelte
│ │ ├── global.css
│ │ └── main.js
│ └── vanilla-javascript/
│ ├── .gitignore
│ ├── bundless.config.js
│ ├── package.json
│ ├── public/
│ │ └── index.html
│ └── src/
│ ├── index.js
│ └── styles.css
├── fixtures/
│ ├── html-page/
│ │ ├── __mirror__/
│ │ │ └── index.html
│ │ ├── __snapshots__
│ │ └── index.html
│ ├── outsider.js
│ ├── resolve-sourcemap/
│ │ ├── __mirror__/
│ │ │ ├── folder/
│ │ │ │ └── main.js
│ │ │ └── index.html
│ │ ├── __snapshots__
│ │ ├── folder/
│ │ │ └── main.js
│ │ └── index.html
│ ├── serve-outside-root/
│ │ ├── __mirror__/
│ │ │ ├── __..__/
│ │ │ │ └── outsider.js
│ │ │ ├── index.html
│ │ │ └── main.js
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ └── main.js
│ ├── simple-js/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ └── main.js
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ └── main.js
│ ├── with-alias-plugin/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ ├── main.tsx
│ │ │ └── text.ts
│ │ ├── __snapshots__
│ │ ├── bundless.config.js
│ │ ├── index.html
│ │ ├── main.tsx
│ │ ├── package.json
│ │ └── text.ts
│ ├── with-assets-imports/
│ │ ├── __mirror__/
│ │ │ ├── dynamic-import.js
│ │ │ ├── file.css.cssjs
│ │ │ ├── index.html
│ │ │ └── main.js
│ │ ├── __snapshots__
│ │ ├── bundless.config.js
│ │ ├── dynamic-import.js
│ │ ├── file.css
│ │ ├── index.html
│ │ └── main.js
│ ├── with-babel-plugin/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ └── main.tsx
│ │ ├── __snapshots__
│ │ ├── bundless.config.js
│ │ ├── index.html
│ │ ├── main.tsx
│ │ └── package.json
│ ├── with-commonjs-transform/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ └── main.jsx
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ ├── main.jsx
│ │ └── package.json
│ ├── with-css/
│ │ ├── __mirror__/
│ │ │ ├── file.css.cssjs
│ │ │ ├── file.js
│ │ │ ├── index.html
│ │ │ └── main.js
│ │ ├── __snapshots__
│ │ ├── file.css
│ │ ├── file.js
│ │ ├── index.html
│ │ └── main.js
│ ├── with-css-modules/
│ │ ├── __mirror__/
│ │ │ ├── file.js
│ │ │ ├── file.module.css.cssjs
│ │ │ ├── index.html
│ │ │ └── main.js
│ │ ├── __snapshots__
│ │ ├── file.js
│ │ ├── file.module.css
│ │ ├── index.html
│ │ └── main.js
│ ├── with-custom-assets/
│ │ ├── __mirror__/
│ │ │ ├── file.fakecss.cssjs
│ │ │ ├── file.fakejs
│ │ │ ├── file.js
│ │ │ ├── index.html
│ │ │ ├── main.js
│ │ │ └── x.DAC
│ │ ├── __snapshots__
│ │ ├── bundless.config.js
│ │ ├── file.fakecss
│ │ ├── file.fakejs
│ │ ├── file.js
│ │ ├── index.html
│ │ ├── main.js
│ │ └── x.DAC
│ ├── with-dependencies/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ └── main.js
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ └── main.js
│ ├── with-dependencies-assets/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ └── main.js
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ ├── main.js
│ │ └── package.json
│ ├── with-dynamic-import/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ ├── main.js
│ │ │ └── text.js
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ ├── main.js
│ │ └── text.js
│ ├── with-env-plugin/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ └── main.tsx
│ │ ├── __snapshots__
│ │ ├── bundless.config.js
│ │ ├── envfile
│ │ ├── index.html
│ │ └── main.tsx
│ ├── with-esbuild-plugins/
│ │ ├── __mirror__/
│ │ │ ├── fake.js
│ │ │ ├── file.gql
│ │ │ ├── index.html
│ │ │ └── main.js
│ │ ├── __snapshots__
│ │ ├── bundless.config.js
│ │ ├── fake.js
│ │ ├── file.gql
│ │ ├── index.html
│ │ └── main.js
│ ├── with-imports/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ ├── main.js
│ │ │ └── text.js
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ ├── main.js
│ │ └── text.js
│ ├── with-json/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ ├── main.js
│ │ │ └── text.json
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ ├── main.js
│ │ └── text.json
│ ├── with-linked-workspace/
│ │ ├── __mirror__/
│ │ │ ├── __..__/
│ │ │ │ └── with-many-dependencies/
│ │ │ │ └── main.js
│ │ │ ├── index.html
│ │ │ └── main.js
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ ├── main.js
│ │ └── package.json
│ ├── with-links/
│ │ ├── __mirror__/
│ │ │ └── index.html
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ ├── public/
│ │ │ ├── manifest.json
│ │ │ └── styles1.css
│ │ └── styles2.css
│ ├── with-many-dependencies/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ └── main.js
│ │ ├── __snapshots__
│ │ ├── bundless.config.js
│ │ ├── index.html
│ │ ├── main.js
│ │ └── package.json
│ ├── with-many-entries/
│ │ ├── __mirror__/
│ │ │ ├── a/
│ │ │ │ ├── index.html
│ │ │ │ ├── main.css.cssjs
│ │ │ │ └── main.js
│ │ │ ├── b/
│ │ │ │ ├── index.html
│ │ │ │ ├── main.js
│ │ │ │ └── text.js
│ │ │ └── common.css.cssjs
│ │ ├── __snapshots__
│ │ ├── a/
│ │ │ ├── index.html
│ │ │ ├── main.css
│ │ │ └── main.js
│ │ ├── b/
│ │ │ ├── index.html
│ │ │ ├── main.js
│ │ │ └── text.js
│ │ ├── bundless.config.js
│ │ └── common.css
│ ├── with-node-polyfills/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ ├── main.js
│ │ │ └── path
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ └── main.js
│ ├── with-sourcemaps/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ ├── js.js
│ │ │ ├── main.ts
│ │ │ └── text.js
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ ├── js.js
│ │ ├── main.ts
│ │ ├── package.json
│ │ └── text.js
│ ├── with-svelte/
│ │ ├── App.svelte
│ │ ├── __mirror__/
│ │ │ ├── App.svelte
│ │ │ ├── App.svelte.css
│ │ │ ├── index.html
│ │ │ └── main.js
│ │ ├── __snapshots__
│ │ ├── bundless.config.js
│ │ ├── index.html
│ │ ├── main.js
│ │ └── package.json
│ ├── with-tsconfig-paths/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ ├── main.tsx
│ │ │ └── text.ts
│ │ ├── __snapshots__
│ │ ├── bundless.config.js
│ │ ├── index.html
│ │ ├── main.tsx
│ │ ├── package.json
│ │ └── text.ts
│ ├── with-tsx/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ ├── main.tsx
│ │ │ ├── text.ts
│ │ │ └── utils.ts
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ ├── main.tsx
│ │ ├── text.ts
│ │ └── utils.ts
│ ├── with-typescript/
│ │ ├── __mirror__/
│ │ │ ├── index.html
│ │ │ ├── main.ts
│ │ │ ├── text.ts
│ │ │ └── utils.ts
│ │ ├── __snapshots__
│ │ ├── index.html
│ │ ├── main.ts
│ │ ├── text.ts
│ │ └── utils.ts
│ └── with-yarn-berry-paths/
│ ├── __mirror__/
│ │ ├── index.html
│ │ └── main.js
│ ├── __snapshots__
│ ├── index.html
│ └── main.js
├── hmr-test-app/
│ ├── __snapshots__/
│ │ ├── bundless
│ │ ├── snowpack
│ │ └── vite
│ ├── bundless.config.js
│ ├── index.test.ts
│ ├── package.json
│ ├── public/
│ │ ├── bundless/
│ │ │ └── index.html
│ │ ├── index.html
│ │ ├── snowpack/
│ │ │ └── index.html
│ │ └── vite/
│ │ └── index.html
│ ├── snowpack.config.js
│ ├── src/
│ │ ├── bridge.jsx
│ │ ├── file.css
│ │ ├── file.json
│ │ ├── file.jsx
│ │ ├── file.module.css
│ │ ├── file2.js
│ │ ├── imported-many-times.js
│ │ └── main.jsx
│ ├── tsconfig.json
│ └── vite.config.js
├── jest.config.js
├── package.json
├── paged/
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── package.json
│ ├── src/
│ │ ├── client/
│ │ │ ├── context.ts
│ │ │ └── index.ts
│ │ ├── constants.ts
│ │ ├── export.tsx
│ │ ├── index.tsx
│ │ ├── plugin.tsx
│ │ ├── routes.ts
│ │ └── server.tsx
│ └── tsconfig.json
├── plugins/
│ ├── alias/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── babel/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── react-refresh/
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── svelte/
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ └── typescript.ts
│ │ ├── tsconfig.json
│ │ └── yarn-error.log
│ └── tsconfig-paths/
│ ├── package.json
│ ├── src/
│ │ └── index.ts
│ └── tsconfig.json
├── scripts/
│ ├── analyze.ts
│ ├── index.html
│ ├── partition.ts
│ ├── scc.ts
│ ├── topological.ts
│ ├── tsconfig.json
│ └── ws.ts
├── tests/
│ ├── CHANGELOG.md
│ ├── fixtures.test.ts
│ ├── package.json
│ └── utils.ts
├── tsconfig.base.json
├── website/
│ ├── .gitignore
│ ├── components/
│ │ └── GradientBg.tsx
│ ├── constants.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── pages/
│ │ ├── _app.tsx
│ │ ├── docs/
│ │ │ ├── benchmarks.mdx
│ │ │ ├── cli.mdx
│ │ │ ├── config.mdx
│ │ │ ├── faq.mdx
│ │ │ ├── how-it-works.mdx
│ │ │ ├── index.mdx
│ │ │ ├── integrations/
│ │ │ │ ├── alias.mdx
│ │ │ │ ├── babel.mdx
│ │ │ │ └── react-refresh.mdx
│ │ │ └── migration.mdx
│ │ └── index.tsx
│ └── tsconfig.json
└── with-pages/
├── CHANGELOG.md
├── components.tsx
├── export.js
├── index.test.ts
├── package.json
├── pages/
│ ├── about.tsx
│ ├── dynamic-import.tsx
│ ├── folder/
│ │ ├── about.tsx
│ │ └── index.tsx
│ ├── index.tsx
│ └── slugs/
│ ├── [slug].tsx
│ └── all/
│ └── [...slugs].tsx
├── rpc/
│ └── example.ts
└── server.js
SYMBOL INDEX (332 symbols across 90 files)
FILE: bundless/src/build/index.ts
type OwnArgs (line 32) | interface OwnArgs {
function build (line 39) | async function build({
function insertAfterStrings (line 446) | function insertAfterStrings(items, node) {
function MyNode (line 451) | function MyNode(x: Partial<Node>): Node {
function traverseGraphDown (line 455) | function traverseGraphDown(args: {
FILE: bundless/src/cli.ts
function prettyPrintErrors (line 136) | function prettyPrintErrors(fn) {
FILE: bundless/src/client/template.ts
function log (line 42) | function log(...args) {
function reload (line 46) | function reload() {
constant SOCKET_MESSAGE_QUEUE (line 53) | let SOCKET_MESSAGE_QUEUE: HMRPayload[] = []
function _sendSocketMessage (line 55) | function _sendSocketMessage(msg) {
function sendSocketMessage (line 58) | function sendSocketMessage(msg: HMRPayload) {
constant REGISTERED_MODULES (line 68) | const REGISTERED_MODULES: { [path: string]: HotModuleState } = {}
class HotModuleState (line 69) | class HotModuleState {
method constructor (line 77) | constructor(path) {
method lock (line 80) | lock() {
method dispose (line 83) | dispose(callback) {
method invalidate (line 86) | invalidate() {
method decline (line 89) | decline() {
method accept (line 92) | accept(_deps, callback: Function | true = true) {
function createHotContext (line 117) | function createHotContext(fullUrl) {
function runModuleAccept (line 132) | async function runModuleAccept({ path, namespace, updateID }: UpdatePayl...
function runModuleDispose (line 161) | async function runModuleDispose(id) {
function getErrorMessageMappedSource (line 176) | function getErrorMessageMappedSource(message) {
function getErrorStackMappedSource (line 188) | function getErrorStackMappedSource(stack) {
function appendQuery (line 287) | function appendQuery(url: string, query: string) {
class CommonOverlay (line 412) | class CommonOverlay extends HTMLElement {
method isOpen (line 416) | static isOpen() {
method show (line 421) | static show(arg) {
method clear (line 430) | static clear() {
method close (line 436) | close() {
method displayText (line 440) | displayText(selector: string, text: string, linkFiles = false) {
function getAllMatches (line 470) | function getAllMatches(text: string, regex: RegExp) {
class ErrorOverlay (line 486) | class ErrorOverlay extends CommonOverlay {
method constructor (line 491) | constructor(err: OverlayErrorPayload['err']) {
class InfoOverlay (line 520) | class InfoOverlay extends CommonOverlay {
method constructor (line 525) | constructor(info: OverlayInfoOpenPayload['info']) {
FILE: bundless/src/client/types.ts
type HMRPayload (line 1) | type HMRPayload =
type ConnectedPayload (line 11) | interface ConnectedPayload {
type UpdatePayload (line 15) | interface UpdatePayload {
type FullReloadPayload (line 24) | interface FullReloadPayload {
type HotAcceptPayload (line 28) | interface HotAcceptPayload {
type ConnectPayload (line 33) | interface ConnectPayload {
type OverlayErrorPayload (line 37) | interface OverlayErrorPayload {
type OverlayInfoOpenPayload (line 50) | interface OverlayInfoOpenPayload {
type OverlayInfoClosePayload (line 59) | interface OverlayInfoClosePayload {
FILE: bundless/src/config.ts
function getEntries (line 9) | async function getEntries(
type Platform (line 58) | type Platform = 'node' | 'browser'
function normalizeConfig (line 60) | function normalizeConfig(config: Config) {
type Config (line 75) | interface Config {
type PrebundlingConfig (line 98) | interface PrebundlingConfig {
type ServerConfig (line 103) | interface ServerConfig {
function loadConfig (line 134) | function loadConfig(from: string, name = CONFIG_NAME): Config {
type HmrConfig (line 146) | interface HmrConfig {
type BuildConfig (line 160) | interface BuildConfig {
FILE: bundless/src/constants.ts
constant DEFAULT_PORT (line 14) | const DEFAULT_PORT = 3000
constant CLIENT_PUBLIC_PATH (line 15) | const CLIENT_PUBLIC_PATH = `/_hmr_client.js?namespace=${hmrClientNamespa...
constant COMMONJS_ANALYSIS_PATH (line 16) | const COMMONJS_ANALYSIS_PATH = '.bundless/commonjs.json'
constant WEB_MODULES_PATH (line 17) | const WEB_MODULES_PATH = '.bundless/node_modules'
constant BUNDLE_MAP_PATH (line 19) | const BUNDLE_MAP_PATH = '.bundless/bundleMap.json'
constant HMR_SERVER_NAME (line 20) | const HMR_SERVER_NAME = 'esm-hmr'
constant CONFIG_NAME (line 21) | const CONFIG_NAME = 'bundless.config.js'
constant EXAMPLES_FOLDERS (line 23) | const EXAMPLES_FOLDERS = [
constant MAIN_FIELDS (line 30) | const MAIN_FIELDS = ['browser:module', 'browser', 'module', 'main']
constant JS_EXTENSIONS (line 34) | const JS_EXTENSIONS = ['.ts', '.tsx', '.mjs', '.js', '.jsx', '.cjs']
FILE: bundless/src/hmr-graph.ts
type OsAgnosticPath (line 15) | type OsAgnosticPath = string
type ImportPath (line 18) | type ImportPath = string
type HmrNode (line 19) | interface HmrNode {
class HmrGraph (line 30) | class HmrGraph {
method constructor (line 38) | constructor({ root, server }: { root: string; server: net.Server }) {
method sendHmrMessage (line 81) | sendHmrMessage(payload: HMRPayload) {
method ensureEntry (line 105) | ensureEntry(path: string, newNode?: Partial<HmrNode>): HmrNode {
method toString (line 136) | toString() {
method onFileChange (line 171) | async onFileChange({ filePath }: { filePath: string }) {
method traverseUpwards (line 280) | async traverseUpwards({ onTraverse, entries }) {
FILE: bundless/src/logger.ts
constant DEBUG (line 6) | const DEBUG = process.env.DEBUG_BUNDLESS
class Logger (line 7) | class Logger {
method constructor (line 10) | constructor({ prefix = defaultPrefix, silent = false } = {}) {
method print (line 15) | private print(x) {
method log (line 25) | log(...x) {
method warn (line 28) | warn(...x) {
method error (line 31) | error(...x) {
method spinStart (line 37) | spinStart(text: string) {
method spinSucceed (line 43) | spinSucceed(text: string) {
method spinFail (line 49) | spinFail(text: string) {
FILE: bundless/src/middleware/history-fallback.ts
function historyFallbackMiddleware (line 8) | function historyFallbackMiddleware({
function send (line 84) | function send(ctx, resolvedHtml, as = '') {
FILE: bundless/src/middleware/open-in-editor.ts
function openInEditorMiddleware (line 9) | function openInEditorMiddleware({ root }): Middleware {
FILE: bundless/src/middleware/plugins.ts
function pluginsMiddleware (line 8) | function pluginsMiddleware({
FILE: bundless/src/middleware/static-serve.ts
function staticServeMiddleware (line 7) | function staticServeMiddleware(opts: SendOptions): Middleware {
FILE: bundless/src/plugins-executor.ts
type Plugin (line 19) | interface Plugin {
type OnResolveCallback (line 26) | type OnResolveCallback = (
type OnLoadCallback (line 30) | type OnLoadCallback = (
type OnTransformCallback (line 34) | type OnTransformCallback = (
type OnCloseCallback (line 38) | type OnCloseCallback = () => void | Promise<void>
type PluginsExecutorCtx (line 40) | interface PluginsExecutorCtx {
type PluginHooks (line 47) | interface PluginHooks extends esbuild.PluginBuild {
type OnTransformArgs (line 62) | interface OnTransformArgs {
type OnTransformResult (line 69) | interface OnTransformResult {
type Maybe (line 75) | type Maybe<x> = x | undefined | null
type PluginInternalObject (line 77) | type PluginInternalObject<CB> = {
type OnResolved (line 83) | type OnResolved = (
class PluginsExecutor (line 88) | class PluginsExecutor {
method constructor (line 101) | constructor(_args: {
method modulesToPrebundle (line 148) | modulesToPrebundle() {
method matches (line 152) | private matches(
method load (line 170) | async load(arg: esbuild.OnLoadArgs): Promise<Maybe<esbuild.OnLoadResul...
method transform (line 202) | async transform(arg: OnTransformArgs): Promise<OnTransformResult> {
method resolve (line 242) | async resolve(
method close (line 314) | async close() {
method resolveLoadTransform (line 323) | async resolveLoadTransform({
method esbuildPlugins (line 387) | esbuildPlugins() {
method printProfilingResult (line 403) | printProfilingResult() {
method wrapPluginForProfiling (line 454) | private wrapPluginForProfiling(plugin: Plugin): Plugin {
method wrapPluginForEsbuild (line 486) | private wrapPluginForEsbuild(plugin: Plugin): esbuild.Plugin {
function sortPlugins (line 536) | function sortPlugins(plugins?: Plugin[]): [Plugin[], Plugin[]] {
FILE: bundless/src/plugins/assets.ts
function AssetsPlugin (line 12) | function AssetsPlugin({
FILE: bundless/src/plugins/buffer.ts
constant BUFFER_PATH (line 5) | const BUFFER_PATH = '_bundless-node-buffer-polyfill_.js'
function NodeBufferGlobal (line 7) | function NodeBufferGlobal(): Plugin {
FILE: bundless/src/plugins/css.ts
constant CSS_UTILS_PATH (line 11) | const CSS_UTILS_PATH = '_bundless_css_utils.js'
function CssPlugin (line 23) | function CssPlugin({} = {}) {
function codegenCssForDev (line 124) | async function codegenCssForDev(
function codegenCssForProduction (line 157) | function codegenCssForProduction(
FILE: bundless/src/plugins/env.ts
function EnvPlugin (line 9) | function EnvPlugin({
FILE: bundless/src/plugins/esbuild.ts
function EsbuildTransformPlugin (line 10) | function EsbuildTransformPlugin({} = {}) {
function resolveJsxOptions (line 39) | function resolveJsxOptions(options: Config['jsx'] = 'react') {
function printMessage (line 111) | function printMessage(m: Message, code: string) {
FILE: bundless/src/plugins/hmr-client.ts
function HmrClientPlugin (line 11) | function HmrClientPlugin({ getPort }) {
FILE: bundless/src/plugins/html-ingest.ts
constant NAME (line 7) | const NAME = 'html-ingest'
type Options (line 9) | interface Options {
function HtmlIngestPlugin (line 19) | function HtmlIngestPlugin({
function getHtmlScriptsUrls (line 72) | async function getHtmlScriptsUrls(html: string) {
function isRelative (line 100) | function isRelative(x: string) {
FILE: bundless/src/plugins/html-resolver.ts
function HtmlResolverPlugin (line 5) | function HtmlResolverPlugin({} = {}) {
FILE: bundless/src/plugins/html-transform.ts
function HtmlTransformUrlsPlugin (line 5) | function HtmlTransformUrlsPlugin({
FILE: bundless/src/plugins/json.ts
function JSONPlugin (line 6) | function JSONPlugin({} = {}) {
FILE: bundless/src/plugins/resolve-sourcemaps.ts
function ResolveSourcemapPlugin (line 10) | function ResolveSourcemapPlugin({} = {}) {
FILE: bundless/src/plugins/rewrite/commonjs.ts
type OptimizeAnalysisResult (line 11) | interface OptimizeAnalysisResult {
function clearCommonjsAnalysisCache (line 48) | function clearCommonjsAnalysisCache() {
function isOptimizedCjs (line 54) | function isOptimizedCjs(root: string, filename: string) {
type ImportNameSpecifier (line 68) | type ImportNameSpecifier = { importedName: string; localName: string }
function transformCjsImport (line 71) | function transformCjsImport(
function getImportNames (line 82) | function getImportNames(ast: ImportDeclaration) {
function generateCjsImport (line 105) | function generateCjsImport(
function generateNamespaceExport (line 137) | function generateNamespaceExport(mId: string) {
FILE: bundless/src/plugins/rewrite/rewrite.ts
function RewritePlugin (line 24) | function RewritePlugin({ filter = jsTypeRegex } = {}) {
function rewriteImports (line 56) | async function rewriteImports({
FILE: bundless/src/plugins/source-map-support.ts
function SourceMapSupportPlugin (line 8) | function SourceMapSupportPlugin({} = {}) {
FILE: bundless/src/plugins/url-resolver.ts
function UrlResolverPlugin (line 8) | function UrlResolverPlugin({} = {}) {
FILE: bundless/src/prebundle/esbuild.ts
function generateDefineObject (line 58) | function generateDefineObject({
function bundleWithEsBuild (line 104) | async function bundleWithEsBuild({
function makeTsConfig (line 198) | function makeTsConfig({ alias }) {
type BundleMap (line 211) | type BundleMap = Partial<Record<string, string>>
function metafileToBundleMap (line 216) | function metafileToBundleMap(_options: {
function metafileToAnalysis (line 242) | function metafileToAnalysis(_options: {
function metafileToStats (line 277) | function metafileToStats(_options: {
FILE: bundless/src/prebundle/prebundle.ts
function prebundle (line 17) | async function prebundle({ entryPoints, config, root, dest }) {
function getClearDependencyPath (line 81) | function getClearDependencyPath(p: string) {
function getScopedPackageName (line 90) | function getScopedPackageName(path: string): any {
function getPackageName (line 94) | function getPackageName(p: string) {
function makeEntryObject (line 109) | function makeEntryObject(dependenciesPaths: string[]) {
FILE: bundless/src/prebundle/stats.ts
type DependencyType (line 3) | type DependencyType = 'direct' | 'common'
type DependencyStatsMap (line 5) | type DependencyStatsMap = {
type DependencyStats (line 9) | type DependencyStats = { size: number }
type DependencyStatsOutput (line 11) | type DependencyStatsOutput = Record<DependencyType, DependencyStatsMap>
function printStats (line 13) | function printStats(_args: {
constant SIZE_COLUMN_WIDTH (line 44) | const SIZE_COLUMN_WIDTH = 11
function entriesSort (line 47) | function entriesSort([filenameA]: [string, any], [filenameB]: [string, a...
function formatSize (line 52) | function formatSize(size) {
function formatDelta (line 68) | function formatDelta(delta) {
function formatFileInfo (line 74) | function formatFileInfo(
function formatFiles (line 94) | function formatFiles(files: [string, DependencyStats][], padEnd: number) {
FILE: bundless/src/prebundle/support.ts
function isUrl (line 4) | function isUrl(req: string) {
type OptimizeAnalysisResult (line 11) | interface OptimizeAnalysisResult {
function unique (line 15) | function unique<T>(array: T[], key = (x: T): any => x): T[] {
function stripColon (line 28) | function stripColon(input?: string) {
function convertKeys (line 40) | function convertKeys<T>(obj: T, cb: (k: string) => string): T {
function runFunctionOnPaths (line 52) | function runFunctionOnPaths(
FILE: bundless/src/prebundle/traverse.ts
type Args (line 27) | type Args = {
function traverseWithEsbuild (line 37) | async function traverseWithEsbuild({
function traversalGraphPlugin (line 133) | function traversalGraphPlugin({
type TraversalGraph (line 188) | type TraversalGraph = Record<string, string[]>
function metaToTraversalResult (line 193) | function metaToTraversalResult({
FILE: bundless/src/serve.ts
type ServerPluginContext (line 53) | interface ServerPluginContext {
type ServerMiddleware (line 66) | type ServerMiddleware = (ctx: ServerPluginContext) => void
function serve (line 68) | async function serve(config: Config) {
function createDevApp (line 94) | async function createDevApp(server: net.Server, config: Config) {
function getDepsHash (line 336) | async function getDepsHash(root: string) {
function updateHash (line 350) | async function updateHash(hashPath: string, newHash: string) {
function shouldUseFsEvents (line 382) | function shouldUseFsEvents() {
FILE: bundless/src/utils/path.ts
function importPathToFile (line 8) | function importPathToFile(
function fileToImportPath (line 24) | function fileToImportPath(
function osAgnosticPath (line 38) | function osAgnosticPath(absPath: string | undefined, root: string) {
function removeLeadingSlash (line 53) | function removeLeadingSlash(p: string) {
FILE: bundless/src/utils/profiling.ts
constant MS_IN_MINUTE (line 4) | const MS_IN_MINUTE = 60000
constant MS_IN_SECOND (line 5) | const MS_IN_SECOND = 1000
type Quantiles (line 7) | type Quantiles = [number, number, number] | null
type StatsResult (line 9) | interface StatsResult {
type FolderStats (line 15) | interface FolderStats {
function bg (line 20) | function bg(text: string, fn = 'cyan') {
function fg (line 24) | function fg(text, time) {
function ansiChart (line 37) | function ansiChart(
function humanizeDuration (line 74) | function humanizeDuration(value: number) {
function outlier (line 112) | function outlier(quartiles: Quantiles, value: number) {
function stats (line 125) | function stats(folderStats: FolderStats[]): StatsResult {
function humanizeStats (line 148) | function humanizeStats(stats: { [key: string]: StatsResult }): string {
FILE: bundless/src/utils/sourcemaps.ts
function mergeSourceMap (line 4) | function mergeSourceMap(
function genSourceMapString (line 18) | function genSourceMapString(map: RawSourceMap | string | undefined) {
FILE: bundless/src/utils/utils.ts
function generateCodeFrame (line 44) | function generateCodeFrame(
function readBody (line 86) | async function readBody(
function readFile (line 129) | async function readFile(p: string) {
function flatten (line 139) | function flatten<T>(arr: T[][]): T[] {
function needsPrebundle (line 147) | function needsPrebundle(config: Config, p: string) {
function parse (line 188) | function parse(
function appendQuery (line 204) | function appendQuery(url: string, query: string) {
function partition (line 214) | function partition<T>(
class Lock (line 225) | class Lock extends EventEmitter {
method constructor (line 228) | constructor() {
method ready (line 231) | ready() {
method lock (line 234) | lock() {
method wait (line 240) | async wait() {
function prepareError (line 248) | function prepareError(err: Error) {
function isEmpty (line 259) | function isEmpty(map) {
function computeDuration (line 263) | function computeDuration(
function makeLegalIdentifier (line 286) | function makeLegalIdentifier(str) {
FILE: examples/react-javascript/src/app.jsx
function App (line 3) | function App() {
FILE: examples/react-typescript/src/app.tsx
function App (line 3) | function App() {
FILE: fixtures/with-babel-plugin/__mirror__/main.tsx
constant ONE_DAY (line 4) | const ONE_DAY = 864e5;
constant TWO_DAYS (line 5) | const TWO_DAYS = 1728e5;
FILE: fixtures/with-babel-plugin/main.tsx
constant ONE_DAY (line 5) | const ONE_DAY = ms('1 day')
constant TWO_DAYS (line 6) | const TWO_DAYS = ms('2 days')
FILE: fixtures/with-esbuild-plugins/bundless.config.js
method setup (line 11) | setup({ onLoad }) {
method setup (line 24) | setup({ onResolve }) {
FILE: fixtures/with-sourcemaps/__mirror__/js.js
function Comp (line 12) | function Comp() {
FILE: fixtures/with-sourcemaps/__mirror__/main.ts
function Comp (line 11) | function Comp() {
FILE: fixtures/with-sourcemaps/js.js
function Comp (line 12) | function Comp() {
FILE: fixtures/with-sourcemaps/main.ts
function Comp (line 15) | function Comp() {
FILE: fixtures/with-tsx/__mirror__/main.tsx
function jsx (line 2) | function jsx(t, p, children) {
FILE: fixtures/with-tsx/__mirror__/utils.ts
function allCaps (line 1) | function allCaps(x) {
FILE: fixtures/with-tsx/main.tsx
function jsx (line 4) | function jsx(t, p, children) {
FILE: fixtures/with-tsx/utils.ts
function allCaps (line 1) | function allCaps(x: string) {
FILE: fixtures/with-typescript/__mirror__/utils.ts
function allCaps (line 1) | function allCaps(x) {
FILE: fixtures/with-typescript/utils.ts
function allCaps (line 1) | function allCaps(x: string) {
FILE: hmr-test-app/index.test.ts
constant PORT (line 27) | const PORT = 4000
type TestCase (line 32) | type TestCase = {
function start (line 117) | async function start(type) {
function updateFile (line 272) | async function updateFile(compPath, replacer) {
function getWsMessages (line 281) | async function getWsMessages({ doing, timeout = 2000, ws }) {
function registerHotModules (line 301) | async function registerHotModules(traversedFiles, ws) {
function waitUntilCountStabilizes (line 331) | async function waitUntilCountStabilizes(count, releaseTime = 50) {
function defaultJsReplacer (line 351) | function defaultJsReplacer(x) {
function defaultCssReplacer (line 355) | function defaultCssReplacer(x) {
FILE: hmr-test-app/src/bridge.jsx
function App (line 7) | function App() {
FILE: paged/src/client/index.ts
type LoadFunctionContext (line 35) | interface LoadFunctionContext {
type LoadFunction (line 41) | type LoadFunction = (
FILE: paged/src/constants.ts
constant CLIENT_ENTRY (line 4) | const CLIENT_ENTRY = '_bundless_paged_entry_.jsx'
constant ROUTES_ENTRY (line 5) | const ROUTES_ENTRY = '_bundless_paged_routes_.jsx'
FILE: paged/src/export.tsx
function exportPage (line 20) | async function exportPage({
function staticExport (line 49) | async function staticExport({
function flatten (line 149) | function flatten<T>(arr: T[][]): T[] {
FILE: paged/src/plugin.tsx
function Plugin (line 9) | function Plugin({} = {}): PluginType {
function rpcFunctionTemplate (line 144) | function rpcFunctionTemplate({ root, originalCodeFilename, rpcPublicPath...
FILE: paged/src/routes.ts
type Route (line 6) | interface Route {
function nameFromPath (line 55) | function nameFromPath(p: string) {
function getRouteFromPath (line 59) | function getRouteFromPath(relativePath: string) {
function relativePathToPublicPath (line 73) | function relativePathToPublicPath(relativePath: string) {
function invalidateCache (line 83) | function invalidateCache(memoFunction) {
function isDynamicRoute (line 88) | function isDynamicRoute(route: Route) {
FILE: paged/src/server.tsx
function createServer (line 22) | async function createServer({
function MainHtml (line 218) | function MainHtml({ prerenderedHtml, clientScriptSrc, context }) {
function tryRequire (line 245) | function tryRequire(p: string) {
FILE: plugins/alias/src/index.ts
function AliasPlugin (line 9) | function AliasPlugin(options: AliasOptions = {}): Plugin {
constant VOLUME (line 74) | const VOLUME = /^([A-Z]:)/i
constant IS_WINDOWS (line 75) | const IS_WINDOWS = platform() === 'win32'
function matches (line 79) | function matches(pattern: string | RegExp, importee: string) {
function normalizeId (line 97) | function normalizeId(id: string | undefined) {
function getEntries (line 104) | function getEntries({ entries }: AliasOptions): readonly Alias[] {
type AliasOptions (line 118) | interface AliasOptions {
type Alias (line 123) | interface Alias {
function flatten (line 128) | function flatten<T>(arr: T[][]): T[] {
FILE: plugins/babel/src/index.ts
type Options (line 7) | interface Options {
function BabelPlugin (line 22) | function BabelPlugin({
FILE: plugins/react-refresh/src/index.ts
function ReactRefreshPlugin (line 20) | function ReactRefreshPlugin({
function getNonComponentExports (line 161) | function getNonComponentExports(ast: BabelAST) {
function isComponentLikeName (line 192) | function isComponentLikeName(name: string) {
function debounce (line 200) | function debounce(fn: () => void, delay: number) {
function transformHtml (line 208) | function transformHtml(contents) {
function flatten (line 222) | function flatten<T>(arr: T[][]): T[] {
constant THIS_PATH_NAME (line 237) | const THIS_PATH_NAME = '__this_path__'
function parse (line 276) | function parse(
FILE: plugins/svelte/src/index.ts
function SveltePlugin (line 22) | function SveltePlugin(options: PluginOptions = {}): Plugin {
type PluginOptions (line 175) | interface PluginOptions {
FILE: plugins/svelte/src/typescript.ts
method script (line 6) | async script({ content, filename, attributes }) {
FILE: plugins/tsconfig-paths/src/index.ts
function TsconfigPathsPlugin (line 8) | function TsconfigPathsPlugin(options: PluginOptions): Plugin {
type PluginOptions (line 47) | type PluginOptions = {
FILE: scripts/analyze.ts
function anal (line 15) | function anal(meta: esbuild.Metadata) {
type TraversalGraph (line 77) | type TraversalGraph = Record<string, Node>
type Node (line 79) | interface Node {
function metaToTraversalResult (line 84) | function metaToTraversalResult({
function osAgnosticPath (line 176) | function osAgnosticPath(absPath: string | undefined, root: string) {
function groupByDependency (line 199) | function groupByDependency(graph: TraversalGraph, root: string) {
function getScopedPackageName (line 242) | function getScopedPackageName(path: string): any {
function formatBytes (line 246) | function formatBytes(bytes) {
FILE: scripts/partition.ts
function partition (line 5) | function partition(graph: TraversalGraph) {
FILE: scripts/scc.ts
function scc (line 3) | function scc(graph: TraversalGraph) {
FILE: scripts/topological.ts
function getCycles (line 18) | function getCycles /*::<T>*/(
function graphSequencer (line 67) | function graphSequencer /*::<T>*/(
FILE: scripts/ws.ts
constant PORT (line 5) | const PORT = 4000
function main (line 7) | async function main() {
FILE: tests/fixtures.test.ts
function urlToRelativePath (line 154) | function urlToRelativePath(ctx) {
FILE: tests/utils.ts
function timedRun (line 7) | async function timedRun(func) {
function osAgnosticResult (line 14) | function osAgnosticResult(x: TraversalResultType): TraversalResultType {
function normalizePath (line 29) | function normalizePath(filePath: string) {
function isUrl (line 35) | function isUrl(str: string) {
FILE: website/components/GradientBg.tsx
function GradientBg (line 3) | function GradientBg(props) {
FILE: website/constants.ts
constant GITHUB_LINK (line 3) | const GITHUB_LINK = 'https://github.com/remorses/bundless'
FILE: website/pages/_app.tsx
function App (line 7) | function App(props) {
FILE: website/pages/index.tsx
function MyFooter (line 280) | function MyFooter({ ...rest }) {
function Benchmark (line 306) | function Benchmark({
function MyNavbar (line 365) | function MyNavbar({ ...rest }) {
FILE: with-pages/components.tsx
function Paragraph (line 5) | function Paragraph() {
FILE: with-pages/export.js
function start (line 4) | async function start({}) {
FILE: with-pages/index.test.ts
constant PORT (line 7) | const PORT = '9097'
FILE: with-pages/pages/about.tsx
function Page (line 3) | function Page() {
FILE: with-pages/pages/dynamic-import.tsx
function Page (line 6) | function Page() {
FILE: with-pages/pages/folder/about.tsx
function Page (line 3) | function Page() {
FILE: with-pages/pages/folder/index.tsx
function Page (line 3) | function Page() {
FILE: with-pages/pages/index.tsx
function Page (line 5) | function Page() {
FILE: with-pages/pages/slugs/[slug].tsx
function Page (line 3) | function Page() {
function getStaticPaths (line 11) | function getStaticPaths() {
FILE: with-pages/pages/slugs/all/[...slugs].tsx
function Page (line 3) | function Page() {
FILE: with-pages/rpc/example.ts
function example (line 1) | async function example(arg: { echo: string }) {
FILE: with-pages/server.js
function start (line 6) | async function start({ port = 8080 }) {
Condensed preview — 422 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,167K chars).
[
{
"path": ".changeset/README.md",
"chars": 512,
"preview": "# Changesets\n\nHello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that wo"
},
{
"path": ".changeset/config.json",
"chars": 377,
"preview": "{\n \"$schema\": \"https://unpkg.com/@changesets/config@1.4.0/schema.json\",\n \"changelog\": \"@changesets/cli/changelog\","
},
{
"path": ".github/workflows/ci.yml",
"chars": 1422,
"preview": "name: Npm Package\n\non:\n push:\n branches:\n - master\n\njobs:\n build:\n runs-on: ${{ matrix.os }}\n strategy:\n"
},
{
"path": ".gitignore",
"chars": 277,
"preview": "node_modules\ndist\nesm\n.DS_Store\n*.tsbuildinfo\n.ultra.cache.json\n**/web_modules/deps_hash\n**/web_modules/**/**.js\n**/web_"
},
{
"path": ".prettierrc",
"chars": 155,
"preview": "{\n \"arrowParens\": \"always\",\n \"jsxSingleQuote\": true,\n \"tabWidth\": 4,\n \"semi\": false,\n \"singleQuote\": true"
},
{
"path": ".vscode/settings.json",
"chars": 135,
"preview": "{\n \"todo-tree.filtering.excludeGlobs\": [\n \"**/web_modules/**\",\n \"**/web_modules/**\",\n \"**/fixtur"
},
{
"path": "README.md",
"chars": 1445,
"preview": "<div align='center'>\n <br/>\n <br/>\n <h3>bundless</h3>\n <p>Next gen dev server and bundler</p>\n <p>this pr"
},
{
"path": "TODOS.md",
"chars": 2781,
"preview": "- fix stack trace parsing in client, use https://github.com/marvinhagemeister/errorstacks\n- make node polyfills an o"
},
{
"path": "bundless/CHANGELOG.md",
"chars": 1767,
"preview": "# @bundless/cli\n\n## 0.6.0\n\n### Minor Changes\n\n- Fixed problems with yarn berry and missing prebundled packages, better"
},
{
"path": "bundless/bin.js",
"chars": 42,
"preview": "#!/usr/bin/env node\nrequire('./dist/cli')\n"
},
{
"path": "bundless/package.json",
"chars": 2856,
"preview": "{\n \"name\": \"@bundless/cli\",\n \"version\": \"0.6.0\",\n \"description\": \"\",\n \"main\": \"dist/index.js\",\n \"module\":"
},
{
"path": "bundless/src/build/index.ts",
"chars": 17030,
"preview": "import deepmerge from 'deepmerge'\nimport * as esbuild from 'esbuild'\nimport fromEntries from 'fromentries'\nimport fs fro"
},
{
"path": "bundless/src/cli.ts",
"chars": 4026,
"preview": "#!/usr/bin/env node\nrequire('source-map-support').install()\nif (process.argv.includes('--debug')) {\n process.env.DEBU"
},
{
"path": "bundless/src/client/template.ts",
"chars": 14534,
"preview": "// This file runs in the browser.\n\n// injected by serverPluginClient when served\ndeclare const sourceMapSupport: any\ndec"
},
{
"path": "bundless/src/client/types.ts",
"chars": 1128,
"preview": "export type HMRPayload =\n | ConnectedPayload\n | UpdatePayload\n | FullReloadPayload\n | OverlayErrorPayload\n "
},
{
"path": "bundless/src/config.ts",
"chars": 4796,
"preview": "import { CONFIG_NAME, DEFAULT_PORT } from './constants'\nimport findUp from 'find-up'\nimport fs from 'fs'\nimport { Plugin"
},
{
"path": "bundless/src/constants.ts",
"chars": 1558,
"preview": "import { logger } from './logger'\nimport * as esbuild from 'esbuild'\n\nexport let isRunningWithYarnPnp: boolean = false\ne"
},
{
"path": "bundless/src/hmr-graph.ts",
"chars": 12022,
"preview": "import path from 'path'\nimport WebSocket from 'ws'\nimport net from 'net'\nimport crypto from 'crypto'\nimport chalk from '"
},
{
"path": "bundless/src/index.ts",
"chars": 303,
"preview": "export { serve } from './serve'\nexport { build } from './build'\nexport { Config, loadConfig } from './config'\nexport { P"
},
{
"path": "bundless/src/logger.ts",
"chars": 1608,
"preview": "import chalk from 'chalk'\nimport ora, { Ora } from 'ora'\n\nconst defaultPrefix = '[bundless] '\n\nconst DEBUG = process.env"
},
{
"path": "bundless/src/middleware/history-fallback.ts",
"chars": 2840,
"preview": "import { Middleware } from 'koa'\nimport path from 'path'\nimport slash from 'slash'\nimport { logger } from '../logger'\nim"
},
{
"path": "bundless/src/middleware/index.ts",
"chars": 272,
"preview": "export { sourcemapMiddleware } from './sourcemap'\nexport { historyFallbackMiddleware } from './history-fallback'\nexport "
},
{
"path": "bundless/src/middleware/open-in-editor.ts",
"chars": 890,
"preview": "import { logger } from '..'\nimport fs from 'fs'\nimport launchEditor from 'launch-editor'\nimport { importPathToFile } fro"
},
{
"path": "bundless/src/middleware/plugins.ts",
"chars": 2788,
"preview": "import { FSWatcher } from 'chokidar'\nimport { Middleware } from 'koa'\nimport { WEB_MODULES_PATH } from '../constants'\nim"
},
{
"path": "bundless/src/middleware/sourcemap.ts",
"chars": 2149,
"preview": "import chalk from 'chalk'\nimport { Middleware } from 'koa'\nimport path from 'path'\nimport { RawSourceMap } from 'source-"
},
{
"path": "bundless/src/middleware/static-serve.ts",
"chars": 1153,
"preview": "import { Middleware } from 'koa'\nimport send, { SendOptions } from 'koa-send'\nimport { WEB_MODULES_PATH } from '../const"
},
{
"path": "bundless/src/plugins/assets.ts",
"chars": 4759,
"preview": "import { NodeResolvePlugin } from '@esbuild-plugins/all'\nimport * as esbuild from 'esbuild'\nimport escapeStringRegexp fr"
},
{
"path": "bundless/src/plugins/buffer.ts",
"chars": 1324,
"preview": "import * as esbuild from 'esbuild'\nimport { Plugin } from '../plugins-executor'\nimport { importPathToFile, readFile } fr"
},
{
"path": "bundless/src/plugins/css.ts",
"chars": 5469,
"preview": "import { NodeResolvePlugin, resolveAsync } from '@esbuild-plugins/all'\nimport { transform } from 'esbuild'\nimport escape"
},
{
"path": "bundless/src/plugins/env.ts",
"chars": 1765,
"preview": "import dotenv from 'dotenv'\nimport dotenvExpand from 'dotenv-expand'\nimport findUp from 'find-up'\nimport fs from 'fs-ext"
},
{
"path": "bundless/src/plugins/esbuild.ts",
"chars": 4033,
"preview": "import chalk from 'chalk'\nimport * as esbuild from 'esbuild'\nimport { Loader, Message, TransformOptions } from 'esbuild'"
},
{
"path": "bundless/src/plugins/hmr-client.ts",
"chars": 4352,
"preview": "import fs from 'fs-extra'\nimport { CLIENT_PUBLIC_PATH, hmrClientNamespace } from '../constants'\nimport { PluginHooks } f"
},
{
"path": "bundless/src/plugins/html-ingest.ts",
"chars": 3400,
"preview": "import fs from 'fs'\nimport posthtml, { Node, Plugin as PosthtmlPlugin } from 'posthtml'\nimport path from 'path'\nimport {"
},
{
"path": "bundless/src/plugins/html-resolver.ts",
"chars": 1744,
"preview": "import fs from 'fs-extra'\nimport path from 'path'\nimport { PluginHooks } from '../plugins-executor'\n\nexport function Htm"
},
{
"path": "bundless/src/plugins/html-transform.ts",
"chars": 699,
"preview": "import posthtml, { Plugin } from 'posthtml'\nimport { PluginHooks } from '../plugins-executor'\nimport { cleanUrl } from '"
},
{
"path": "bundless/src/plugins/index.ts",
"chars": 787,
"preview": "export { EsbuildTransformPlugin } from './esbuild'\nexport { RewritePlugin } from './rewrite'\nexport { CssPlugin } from '"
},
{
"path": "bundless/src/plugins/json.ts",
"chars": 1032,
"preview": "import { NodeResolvePlugin } from '@esbuild-plugins/all'\nimport { transform } from 'esbuild'\nimport { PluginHooks } from"
},
{
"path": "bundless/src/plugins/resolve-sourcemaps.ts",
"chars": 1853,
"preview": "import chalk from 'chalk'\nimport fs from 'fs'\nimport path from 'path'\nimport { RawSourceMap } from 'source-map'\nimport {"
},
{
"path": "bundless/src/plugins/rewrite/__snapshots__/commonjs.test.ts.snap",
"chars": 2466,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`rewrite commonjs imports 0 \"import React from 'react'\" 1`] = `\"impo"
},
{
"path": "bundless/src/plugins/rewrite/commonjs.test.ts",
"chars": 1031,
"preview": "import { parse } from '../../utils'\nimport { transformCjsImport } from './commonjs'\n\ndescribe('rewrite commonjs imports'"
},
{
"path": "bundless/src/plugins/rewrite/commonjs.ts",
"chars": 5238,
"preview": "import { ImportDeclaration } from '@babel/types'\nimport fs from 'fs-extra'\nimport { isPlainObject } from 'lodash'\nimport"
},
{
"path": "bundless/src/plugins/rewrite/index.ts",
"chars": 26,
"preview": "export * from './rewrite'\n"
},
{
"path": "bundless/src/plugins/rewrite/rewrite.ts",
"chars": 9253,
"preview": "import chalk from 'chalk'\nimport { ImportSpecifier, parse as parseImports } from 'es-module-lexer'\nimport MagicString fr"
},
{
"path": "bundless/src/plugins/source-map-support.ts",
"chars": 1449,
"preview": "import fs from 'fs-extra'\nimport { CLIENT_PUBLIC_PATH } from '../constants'\nimport { PluginHooks } from '../plugins-exec"
},
{
"path": "bundless/src/plugins/url-resolver.ts",
"chars": 1411,
"preview": "import { NodeResolvePlugin } from '@esbuild-plugins/all'\nimport { PluginHooks } from '../plugins-executor'\nimport { impo"
},
{
"path": "bundless/src/plugins-executor.ts",
"chars": 18432,
"preview": "import { O_TRUNC } from 'constants'\nimport * as esbuild from 'esbuild'\nimport { cloneDeep } from 'lodash'\nimport { promi"
},
{
"path": "bundless/src/prebundle/__snapshots__/prebundle.test.ts.snap",
"chars": 296,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`traverseWithEsbuild 1`] = `\nArray [\n \"index.html\",\n \"main.js\",\n "
},
{
"path": "bundless/src/prebundle/esbuild.ts",
"chars": 9615,
"preview": "import * as esbuild from 'esbuild'\nimport { Metafile } from 'esbuild'\nimport fromEntries from 'fromentries'\nimport fs fr"
},
{
"path": "bundless/src/prebundle/index.ts",
"chars": 40,
"preview": "export { prebundle } from './prebundle'\n"
},
{
"path": "bundless/src/prebundle/prebundle.test.ts",
"chars": 910,
"preview": "import memoize from 'micro-memoize'\nimport path from 'path'\nimport { makeEntryObject } from './prebundle'\nimport { trave"
},
{
"path": "bundless/src/prebundle/prebundle.ts",
"chars": 4261,
"preview": "import fs from 'fs-extra'\nimport path from 'path'\nimport chalk from 'chalk'\nimport {\n BUNDLE_MAP_PATH,\n COMMONJS_A"
},
{
"path": "bundless/src/prebundle/stats.ts",
"chars": 3453,
"preview": "import chalk from 'chalk'\n\nexport type DependencyType = 'direct' | 'common'\n\nexport type DependencyStatsMap = {\n [fil"
},
{
"path": "bundless/src/prebundle/support.ts",
"chars": 1864,
"preview": "import { Metafile } from 'esbuild'\nimport { forOwn, isPlainObject } from 'lodash'\n\nexport function isUrl(req: string) {\n"
},
{
"path": "bundless/src/prebundle/traverse.ts",
"chars": 9343,
"preview": "import deepmerge from 'deepmerge'\nimport * as esbuild from 'esbuild'\nimport { build, BuildOptions, Metafile, Plugin } fr"
},
{
"path": "bundless/src/serve.ts",
"chars": 12419,
"preview": "import chalk from 'chalk'\nimport chokidar, { FSWatcher } from 'chokidar'\nimport { createHash } from 'crypto'\nimport * as"
},
{
"path": "bundless/src/utils/index.ts",
"chars": 76,
"preview": "export * from './sourcemaps'\nexport * from './path'\nexport * from './utils'\n"
},
{
"path": "bundless/src/utils/path.test.ts",
"chars": 2285,
"preview": "import os from 'os'\nimport path from 'path'\nimport { fileToImportPath, importPathToFile } from './path'\n\nconst root = pa"
},
{
"path": "bundless/src/utils/path.ts",
"chars": 1629,
"preview": "import path from 'path'\nimport defaultPathImpl from 'path'\nimport slash from 'slash'\n\nexport const dotdotEncoding = '__."
},
{
"path": "bundless/src/utils/profiling.test.ts",
"chars": 400,
"preview": "import { ansiChart, humanizeStats, stats } from './profiling'\n\ntest('ansiChart', () => {\n const res = ansiChart(data,"
},
{
"path": "bundless/src/utils/profiling.ts",
"chars": 4494,
"preview": "import chalk from 'chalk'\nimport { max, quantile } from 'simple-statistics'\n\nconst MS_IN_MINUTE = 60000\nconst MS_IN_SECO"
},
{
"path": "bundless/src/utils/sourcemaps.ts",
"chars": 704,
"preview": "import merge from 'merge-source-map'\nimport { RawSourceMap, } from 'source-map'\n\nexport function mergeSourceMap(\n ol"
},
{
"path": "bundless/src/utils/utils.ts",
"chars": 9162,
"preview": "import { parse as _parse } from '@babel/parser'\nimport picomatch from 'picomatch'\nimport { ParserOptions } from '@babel/"
},
{
"path": "bundless/tsconfig.json",
"chars": 150,
"preview": "{\n \"extends\": \"../tsconfig.base.json\",\n \"compilerOptions\": {\n \"rootDir\": \"src\",\n \"outDir\": \"dist\"\n "
},
{
"path": "examples/react-javascript/.gitignore",
"chars": 33,
"preview": ".bundless\nnode_modules\n.DS_Store\n"
},
{
"path": "examples/react-javascript/bundless.config.js",
"chars": 184,
"preview": "const { ReactRefreshPlugin } = require('@bundless/plugin-react-refresh')\n\n/**\n * @type { import('@bundless/cli').Config "
},
{
"path": "examples/react-javascript/package.json",
"chars": 413,
"preview": "{\n \"name\": \"example-react-javascript\",\n \"private\": true,\n \"version\": \"1.0.0\",\n \"scripts\": {\n \"dev\": \""
},
{
"path": "examples/react-javascript/public/index.html",
"chars": 145,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n <script type=\"module\" src=\"/src/index.jsx\"></script>\n <div id=\"root\"></"
},
{
"path": "examples/react-javascript/src/app.jsx",
"chars": 222,
"preview": "import React from 'react'\n\nexport default function App() {\n return (\n <div className='App'>\n <h1>He"
},
{
"path": "examples/react-javascript/src/index.jsx",
"chars": 237,
"preview": "import React from 'react'\nimport ReactDOM from 'react-dom'\n\nimport App from './app'\n\nconst rootElement = document.getEle"
},
{
"path": "examples/react-javascript/src/styles.css",
"chars": 62,
"preview": ".App {\n font-family: sans-serif;\n text-align: center;\n}\n"
},
{
"path": "examples/react-typescript/.gitignore",
"chars": 33,
"preview": ".bundless\nnode_modules\n.DS_Store\n"
},
{
"path": "examples/react-typescript/bundless.config.js",
"chars": 184,
"preview": "const { ReactRefreshPlugin } = require('@bundless/plugin-react-refresh')\n\n/**\n * @type { import('@bundless/cli').Config "
},
{
"path": "examples/react-typescript/package.json",
"chars": 413,
"preview": "{\n \"name\": \"example-react-typescript\",\n \"private\": true,\n \"version\": \"1.0.0\",\n \"scripts\": {\n \"dev\": \""
},
{
"path": "examples/react-typescript/public/index.html",
"chars": 145,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n <script type=\"module\" src=\"/src/index.tsx\"></script>\n <div id=\"root\"></"
},
{
"path": "examples/react-typescript/src/app.tsx",
"chars": 222,
"preview": "import React from 'react'\n\nexport default function App() {\n return (\n <div className='App'>\n <h1>He"
},
{
"path": "examples/react-typescript/src/index.tsx",
"chars": 237,
"preview": "import React from 'react'\nimport ReactDOM from 'react-dom'\n\nimport App from './app'\n\nconst rootElement = document.getEle"
},
{
"path": "examples/react-typescript/src/styles.css",
"chars": 62,
"preview": ".App {\n font-family: sans-serif;\n text-align: center;\n}\n"
},
{
"path": "examples/react-typescript/tsconfig.json",
"chars": 207,
"preview": "{\n \"compilerOptions\": {\n \"rootDir\": \"src\",\n \"esModuleInterop\": true,\n \"skipLibCheck\": true,\n "
},
{
"path": "examples/svelte/.gitignore",
"chars": 37,
"preview": "node_modules\nout\n.bundless\n.DS_Store\n"
},
{
"path": "examples/svelte/bundless.config.js",
"chars": 165,
"preview": "const { SveltePlugin } = require('@bundless/plugin-svelte')\n\n/**\n * @type { import('@bundless/cli').Config }\n */\nmodule."
},
{
"path": "examples/svelte/package.json",
"chars": 306,
"preview": "{\n \"name\": \"svelte-example\",\n \"version\": \"1.0.0\",\n \"private\": true,\n \"scripts\": {\n \"build\": \"bundless"
},
{
"path": "examples/svelte/public/index.html",
"chars": 359,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"widt"
},
{
"path": "examples/svelte/scripts/setupTypeScript.js",
"chars": 3846,
"preview": "// @ts-check\n\n/** This script modifies the project to support TS code in .svelte files like:\n\n <script lang=\"ts\">\n \tex"
},
{
"path": "examples/svelte/src/App.svelte",
"chars": 590,
"preview": "<script>\n export let name\n</script>\n\n<main>\n <h1>Hello {name}!</h1>\n <p>\n This is an example svelte appl"
},
{
"path": "examples/svelte/src/global.css",
"chars": 890,
"preview": "html, body {\n\tposition: relative;\n\twidth: 100%;\n\theight: 100%;\n}\n\nbody {\n\tcolor: #333;\n\tmargin: 0;\n\tpadding: 8px;\n\tbox-s"
},
{
"path": "examples/svelte/src/main.js",
"chars": 169,
"preview": "import './global.css'\nimport App from './App.svelte'\n\nconst app = new App({\n target: document.body,\n props: {\n "
},
{
"path": "examples/vanilla-javascript/.gitignore",
"chars": 33,
"preview": ".bundless\nnode_modules\n.DS_Store\n"
},
{
"path": "examples/vanilla-javascript/bundless.config.js",
"chars": 72,
"preview": "/**\n * @type { import('@bundless/cli').Config }\n */\nmodule.exports = {}\n"
},
{
"path": "examples/vanilla-javascript/package.json",
"chars": 341,
"preview": "{\n \"name\": \"example-vanilla-javascript\",\n \"private\": true,\n \"version\": \"1.0.0\",\n \"scripts\": {\n \"dev\":"
},
{
"path": "examples/vanilla-javascript/public/index.html",
"chars": 114,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n <script type=\"module\" src=\"/src/index.js\"></script>\n </body>\n</html>\n"
},
{
"path": "examples/vanilla-javascript/src/index.js",
"chars": 145,
"preview": "var node = document.createElement('p')\nvar textnode = document.createTextNode('Hey!')\nnode.appendChild(textnode)\ndocumen"
},
{
"path": "examples/vanilla-javascript/src/styles.css",
"chars": 62,
"preview": "body {\n font-family: sans-serif;\n text-align: center;\n}\n"
},
{
"path": "fixtures/html-page/__mirror__/index.html",
"chars": 200,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n<script type=\"module\" src=\"/_hmr_client.js?namespace=hmr-client\"></script>\n\n <h"
},
{
"path": "fixtures/html-page/__snapshots__",
"chars": 787,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`snapshots fixtures/html-page: build 1`] = `\nArray [\n \"index.html\","
},
{
"path": "fixtures/html-page/index.html",
"chars": 124,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n <h1>My First Heading</h1>\n\n <p>My first paragraph.</p>\n </body>\n</ht"
},
{
"path": "fixtures/outsider.js",
"chars": 42,
"preview": "export default 'Hi, i am outside of root'\n"
},
{
"path": "fixtures/resolve-sourcemap/__mirror__/folder/main.js",
"chars": 222,
"preview": "console.log('Hello world!')\n\nvar node = document.createElement('LI') \nvar textnode = document.createTextNode('works!') \n"
},
{
"path": "fixtures/resolve-sourcemap/__mirror__/index.html",
"chars": 277,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n<script type=\"module\" src=\"/_hmr_client.js?namespace=hmr-client\"></script>\n\n <s"
},
{
"path": "fixtures/resolve-sourcemap/__snapshots__",
"chars": 1138,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`snapshots fixtures/resolve-sourcemap: build 1`] = `\nArray [\n \"inde"
},
{
"path": "fixtures/resolve-sourcemap/folder/main.js",
"chars": 214,
"preview": "console.log('Hello world!')\n\nvar node = document.createElement('LI') \nvar textnode = document.createTextNode('works!') \n"
},
{
"path": "fixtures/resolve-sourcemap/index.html",
"chars": 186,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n <script type=\"module\" src=\"/folder/main.js\"></script>\n <h1>My First Hea"
},
{
"path": "fixtures/serve-outside-root/__mirror__/__..__/outsider.js",
"chars": 42,
"preview": "export default 'Hi, i am outside of root'\n"
},
{
"path": "fixtures/serve-outside-root/__mirror__/index.html",
"chars": 200,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n<script type=\"module\" src=\"/_hmr_client.js?namespace=hmr-client\"></script>\n\n <s"
},
{
"path": "fixtures/serve-outside-root/__mirror__/main.js",
"chars": 227,
"preview": "import text from '/__..__/outsider.js?namespace=file&t=0'\n\nconsole.log(text)\n\nvar node = document.createElement('pre') \n"
},
{
"path": "fixtures/serve-outside-root/__snapshots__",
"chars": 1473,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`snapshots fixtures/serve-outside-root: build 1`] = `\nArray [\n \"ind"
},
{
"path": "fixtures/serve-outside-root/index.html",
"chars": 109,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n <script type=\"module\" src=\"/main.js\"></script>\n </body>\n</html>\n"
},
{
"path": "fixtures/serve-outside-root/main.js",
"chars": 200,
"preview": "import text from '../outsider'\n\nconsole.log(text)\n\nvar node = document.createElement('pre') \nvar textnode = document.cre"
},
{
"path": "fixtures/simple-js/__mirror__/index.html",
"chars": 270,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n<script type=\"module\" src=\"/_hmr_client.js?namespace=hmr-client\"></script>\n\n <s"
},
{
"path": "fixtures/simple-js/__mirror__/main.js",
"chars": 181,
"preview": "console.log('Hello world!')\n\nvar node = document.createElement('LI') \nvar textnode = document.createTextNode('works!') \n"
},
{
"path": "fixtures/simple-js/__snapshots__",
"chars": 1078,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`snapshots fixtures/simple-js: build 1`] = `\nArray [\n \"index.html\","
},
{
"path": "fixtures/simple-js/index.html",
"chars": 179,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n <script type=\"module\" src=\"/main.js\"></script>\n <h1>My First Heading</h"
},
{
"path": "fixtures/simple-js/main.js",
"chars": 181,
"preview": "console.log('Hello world!')\n\nvar node = document.createElement('LI') \nvar textnode = document.createTextNode('works!') \n"
},
{
"path": "fixtures/with-alias-plugin/__mirror__/index.html",
"chars": 201,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n<script type=\"module\" src=\"/_hmr_client.js?namespace=hmr-client\"></script>\n\n <s"
},
{
"path": "fixtures/with-alias-plugin/__mirror__/main.tsx",
"chars": 1011,
"preview": "import {text} from \"/text.ts?namespace=file&t=0\";\nimport React from \"/.bundless/node_modules/preact/compat/dist/compat.m"
},
{
"path": "fixtures/with-alias-plugin/__mirror__/text.ts",
"chars": 338,
"preview": "export const text = \"virtual\";\n\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9Vc2"
},
{
"path": "fixtures/with-alias-plugin/__snapshots__",
"chars": 1895,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`snapshots fixtures/with-alias-plugin: build 1`] = `\nArray [\n \"inde"
},
{
"path": "fixtures/with-alias-plugin/bundless.config.js",
"chars": 589,
"preview": "const path = require('path')\nconst { AliasPlugin } = require('@bundless/plugin-alias')\n\nmodule.exports = {\n build: {\n"
},
{
"path": "fixtures/with-alias-plugin/index.html",
"chars": 110,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n <script type=\"module\" src=\"/main.tsx\"></script>\n </body>\n</html>\n"
},
{
"path": "fixtures/with-alias-plugin/main.tsx",
"chars": 227,
"preview": "// @jsx jsx\nimport { text } from '@virtual'\nimport React from 'react'\n\nconst node = document.createElement('pre')\nnode.a"
},
{
"path": "fixtures/with-alias-plugin/package.json",
"chars": 171,
"preview": "{\n \"name\": \"fixtures-with-alias\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"dependencies\": {\n \"preact\""
},
{
"path": "fixtures/with-alias-plugin/text.ts",
"chars": 30,
"preview": "export const text = 'virtual'\n"
},
{
"path": "fixtures/with-assets-imports/__mirror__/dynamic-import.js",
"chars": 32,
"preview": "export default 'dynamic import'\n"
},
{
"path": "fixtures/with-assets-imports/__mirror__/file.css.cssjs",
"chars": 567,
"preview": "import * as __HMR__ from '/_hmr_client.js?namespace=hmr-client'; import.meta.hot = __HMR__.createHotContext(import.meta"
},
{
"path": "fixtures/with-assets-imports/__mirror__/index.html",
"chars": 200,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n<script type=\"module\" src=\"/_hmr_client.js?namespace=hmr-client\"></script>\n\n <s"
},
{
"path": "fixtures/with-assets-imports/__mirror__/main.js",
"chars": 330,
"preview": "import image from '/image.png?namespace=file&t=0'\nimport '/file.css.cssjs?namespace=file&t=0'\n\nconsole.log(image)\n\nconst"
},
{
"path": "fixtures/with-assets-imports/__snapshots__",
"chars": 2453,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`snapshots fixtures/with-assets-imports: build 1`] = `\nArray [\n \"ch"
},
{
"path": "fixtures/with-assets-imports/bundless.config.js",
"chars": 68,
"preview": "module.exports = {\n build: {\n // basePath: '/base'\n }\n}"
},
{
"path": "fixtures/with-assets-imports/dynamic-import.js",
"chars": 32,
"preview": "export default 'dynamic import'\n"
},
{
"path": "fixtures/with-assets-imports/file.css",
"chars": 36,
"preview": "body {\n background-color: aqua;\n}"
},
{
"path": "fixtures/with-assets-imports/index.html",
"chars": 109,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n <script type=\"module\" src=\"/main.js\"></script>\n </body>\n</html>\n"
},
{
"path": "fixtures/with-assets-imports/main.js",
"chars": 270,
"preview": "import image from './image.png'\nimport './file.css'\n\nconsole.log(image)\n\nconst node = document.createElement('div')\nvar "
},
{
"path": "fixtures/with-babel-plugin/__mirror__/index.html",
"chars": 242,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n<script type=\"module\" src=\"/_hmr_client.js?namespace=hmr-client\"></script>\n\n <s"
},
{
"path": "fixtures/with-babel-plugin/__mirror__/main.tsx",
"chars": 2004,
"preview": "import _styled from \"/.bundless/node_modules/styled-components/dist/styled-components.esm.js.js?namespace=file&t=0\";\nimp"
},
{
"path": "fixtures/with-babel-plugin/__snapshots__",
"chars": 3575,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`snapshots fixtures/with-babel-plugin: build 1`] = `\nArray [\n \"inde"
},
{
"path": "fixtures/with-babel-plugin/bundless.config.js",
"chars": 238,
"preview": "const { BabelPlugin } = require('@bundless/plugin-babel')\n\nmodule.exports = {\n plugins: [\n BabelPlugin({\n "
},
{
"path": "fixtures/with-babel-plugin/index.html",
"chars": 141,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n <script type=\"module\" src=\"/main.tsx\"></script>\n <div id=\"main\" ><div/>"
},
{
"path": "fixtures/with-babel-plugin/main.tsx",
"chars": 371,
"preview": "import ms from 'ms.macro'\nimport React from 'react'\nimport dom from 'react-dom'\n\nconst ONE_DAY = ms('1 day')\nconst TWO_D"
},
{
"path": "fixtures/with-babel-plugin/package.json",
"chars": 220,
"preview": "{\n \"name\": \"fixtures-with-babel\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"dependencies\": {\n \"babel-p"
},
{
"path": "fixtures/with-commonjs-transform/__mirror__/index.html",
"chars": 231,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n<script type=\"module\" src=\"/_hmr_client.js?namespace=hmr-client\"></script>\n\n <s"
},
{
"path": "fixtures/with-commonjs-transform/__mirror__/main.jsx",
"chars": 2362,
"preview": "import react_cjsImport0 from \"/.bundless/node_modules/react/index.js.js?namespace=file&t=0\"; const React = react_cjsImpo"
},
{
"path": "fixtures/with-commonjs-transform/__snapshots__",
"chars": 4001,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`snapshots fixtures/with-commonjs-transform: build 1`] = `\nArray [\n "
},
{
"path": "fixtures/with-commonjs-transform/index.html",
"chars": 140,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n <script type=\"module\" src=\"/main.jsx\"></script>\n <div id=\"root\"></div>\n"
},
{
"path": "fixtures/with-commonjs-transform/main.jsx",
"chars": 476,
"preview": "import React from 'react'\nimport * as ReactNamespace from 'react'\nimport { useState } from 'react'\n\nconsole.log('useStat"
},
{
"path": "fixtures/with-commonjs-transform/package.json",
"chars": 194,
"preview": "{\n \"name\": \"fixtures-with-commonjs-transform\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"dependencies\": {\n "
},
{
"path": "fixtures/with-css/__mirror__/file.css.cssjs",
"chars": 607,
"preview": "import * as __HMR__ from '/_hmr_client.js?namespace=hmr-client'; import.meta.hot = __HMR__.createHotContext(import.meta"
},
{
"path": "fixtures/with-css/__mirror__/file.js",
"chars": 70,
"preview": "export const text = 'This has been made in 2020, what a shitty year'\n\n"
},
{
"path": "fixtures/with-css/__mirror__/index.html",
"chars": 200,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n<script type=\"module\" src=\"/_hmr_client.js?namespace=hmr-client\"></script>\n\n <s"
},
{
"path": "fixtures/with-css/__mirror__/main.js",
"chars": 400,
"preview": "import * as __HMR__ from '/_hmr_client.js?namespace=hmr-client'; import.meta.hot = __HMR__.createHotContext(import.meta"
},
{
"path": "fixtures/with-css/__snapshots__",
"chars": 2208,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`snapshots fixtures/with-css: build 1`] = `\nArray [\n \"index.css\",\n "
},
{
"path": "fixtures/with-css/file.css",
"chars": 70,
"preview": "body {\n background: lightcoral;\n}\n\npre {\n background: white;\n}\n\n"
},
{
"path": "fixtures/with-css/file.js",
"chars": 70,
"preview": "export const text = 'This has been made in 2020, what a shitty year'\n\n"
},
{
"path": "fixtures/with-css/index.html",
"chars": 109,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n <script type=\"module\" src=\"/main.js\"></script>\n </body>\n</html>\n"
},
{
"path": "fixtures/with-css/main.js",
"chars": 228,
"preview": "import './file.css'\nimport { text } from './file'\nconst node = document.createElement('pre')\nnode.appendChild(document.c"
},
{
"path": "fixtures/with-css-modules/__mirror__/file.js",
"chars": 70,
"preview": "export const text = 'This has been made in 2020, what a shitty year'\n\n"
},
{
"path": "fixtures/with-css-modules/__mirror__/file.module.css.cssjs",
"chars": 569,
"preview": "import * as __HMR__ from '/_hmr_client.js?namespace=hmr-client'; import.meta.hot = __HMR__.createHotContext(import.meta"
},
{
"path": "fixtures/with-css-modules/__mirror__/index.html",
"chars": 200,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n<script type=\"module\" src=\"/_hmr_client.js?namespace=hmr-client\"></script>\n\n <s"
},
{
"path": "fixtures/with-css-modules/__mirror__/main.js",
"chars": 453,
"preview": "import * as __HMR__ from '/_hmr_client.js?namespace=hmr-client'; import.meta.hot = __HMR__.createHotContext(import.meta"
},
{
"path": "fixtures/with-css-modules/__snapshots__",
"chars": 2275,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`snapshots fixtures/with-css-modules: build 1`] = `\nArray [\n \"index"
},
{
"path": "fixtures/with-css-modules/file.js",
"chars": 70,
"preview": "export const text = 'This has been made in 2020, what a shitty year'\n\n"
},
{
"path": "fixtures/with-css-modules/file.module.css",
"chars": 37,
"preview": ".textLarge {\n font-size: 100px;\n}\n"
},
{
"path": "fixtures/with-css-modules/index.html",
"chars": 109,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n <script type=\"module\" src=\"/main.js\"></script>\n </body>\n</html>\n"
},
{
"path": "fixtures/with-css-modules/main.js",
"chars": 281,
"preview": "import classNames from './file.module.css'\nimport { text } from './file'\n\nconsole.log({ classNames })\n\nconst node = docu"
},
{
"path": "fixtures/with-custom-assets/__mirror__/file.fakecss.cssjs",
"chars": 561,
"preview": "import * as __HMR__ from '/_hmr_client.js?namespace=hmr-client'; import.meta.hot = __HMR__.createHotContext(import.meta"
},
{
"path": "fixtures/with-custom-assets/__mirror__/file.fakejs",
"chars": 35,
"preview": "export default \"i am file.fakejs\";\n"
},
{
"path": "fixtures/with-custom-assets/__mirror__/file.js",
"chars": 301,
"preview": "import dat from '/file.dat?namespace=file&t=0'\nimport svg from '/file.svg?namespace=file&t=0'\n\nimport('/x.DAC?namespace="
},
{
"path": "fixtures/with-custom-assets/__mirror__/index.html",
"chars": 200,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n<script type=\"module\" src=\"/_hmr_client.js?namespace=hmr-client\"></script>\n\n <s"
},
{
"path": "fixtures/with-custom-assets/__mirror__/main.js",
"chars": 473,
"preview": "import * as __HMR__ from '/_hmr_client.js?namespace=hmr-client'; import.meta.hot = __HMR__.createHotContext(import.meta"
},
{
"path": "fixtures/with-custom-assets/__mirror__/x.DAC",
"chars": 23,
"preview": "export default \"/x.DAC\""
},
{
"path": "fixtures/with-custom-assets/__snapshots__",
"chars": 3625,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`snapshots fixtures/with-custom-assets: build 1`] = `\nArray [\n \"chu"
},
{
"path": "fixtures/with-custom-assets/bundless.config.js",
"chars": 169,
"preview": "module.exports = {\n loader: {\n '.dat': 'file',\n '.DAC': 'file',\n '.svg': 'dataurl',\n '.fa"
},
{
"path": "fixtures/with-custom-assets/file.fakecss",
"chars": 30,
"preview": "body {\n background: blue;\n}"
},
{
"path": "fixtures/with-custom-assets/file.fakejs",
"chars": 58,
"preview": "\nexport default 'i am file.fakejs'\n\nexport type x = string"
},
{
"path": "fixtures/with-custom-assets/file.js",
"chars": 247,
"preview": "import dat from './file.dat'\nimport svg from './file.svg'\n\nimport('./x.DAC').then(console.log)\nconsole.log({ dat })\n\ncon"
},
{
"path": "fixtures/with-custom-assets/index.html",
"chars": 109,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n <script type=\"module\" src=\"/main.js\"></script>\n </body>\n</html>\n"
},
{
"path": "fixtures/with-custom-assets/main.js",
"chars": 283,
"preview": "import { text } from './file'\nimport './file.fakecss'\nimport fakejs from './file.fakejs'\n\nconst node = document.createEl"
},
{
"path": "fixtures/with-custom-assets/x.DAC",
"chars": 0,
"preview": ""
},
{
"path": "fixtures/with-dependencies/__mirror__/index.html",
"chars": 200,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n<script type=\"module\" src=\"/_hmr_client.js?namespace=hmr-client\"></script>\n\n <s"
},
{
"path": "fixtures/with-dependencies/__mirror__/main.js",
"chars": 426,
"preview": "import slash_cjsImport0 from \"/.bundless/node_modules/slash/index.js.js?namespace=file&t=0\"; const slash = slash_cjsImpo"
},
{
"path": "fixtures/with-dependencies/__snapshots__",
"chars": 1511,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`snapshots fixtures/with-dependencies: build 1`] = `\nArray [\n \"inde"
},
{
"path": "fixtures/with-dependencies/index.html",
"chars": 109,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n <script type=\"module\" src=\"/main.js\"></script>\n </body>\n</html>\n"
},
{
"path": "fixtures/with-dependencies/main.js",
"chars": 250,
"preview": "import slash from 'slash'\n\nconsole.log('Hello world!')\n\nconst text = slash('.\\\\path\\\\to\\\\something')\n\nvar node = documen"
},
{
"path": "fixtures/with-dependencies-assets/__mirror__/index.html",
"chars": 200,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n<script type=\"module\" src=\"/_hmr_client.js?namespace=hmr-client\"></script>\n\n <s"
},
{
"path": "fixtures/with-dependencies-assets/__mirror__/main.js",
"chars": 279,
"preview": "import tailwindcss_dist_base_css_cjsImport0 from \"/.bundless/node_modules/tailwindcss/dist/base.css.js?namespace=file&t="
},
{
"path": "fixtures/with-dependencies-assets/__snapshots__",
"chars": 1860,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`snapshots fixtures/with-dependencies-assets: build 1`] = `\nArray [\n"
},
{
"path": "fixtures/with-dependencies-assets/index.html",
"chars": 109,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n <script type=\"module\" src=\"/main.js\"></script>\n </body>\n</html>\n"
},
{
"path": "fixtures/with-dependencies-assets/main.js",
"chars": 190,
"preview": "import 'tailwindcss/dist/base.css'\n\nconsole.log('Hello world!!!!!')\n\nconst node = document.createElement('pre')\ndocument"
},
{
"path": "fixtures/with-dependencies-assets/package.json",
"chars": 181,
"preview": "{\n \"name\": \"fixtures-with-dependencies-assets\",\n \"version\": \"0.0.0\",\n \"main\": \"main.js\",\n \"private\": true,\n "
},
{
"path": "fixtures/with-dynamic-import/__mirror__/index.html",
"chars": 200,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n<script type=\"module\" src=\"/_hmr_client.js?namespace=hmr-client\"></script>\n\n <s"
},
{
"path": "fixtures/with-dynamic-import/__mirror__/main.js",
"chars": 223,
"preview": "\nimport('/text.js?namespace=file&t=0').then(({ text }) => {\n var node = document.createElement('LI')\n var textNode"
},
{
"path": "fixtures/with-dynamic-import/__mirror__/text.js",
"chars": 27,
"preview": "export const text = 'CIAO!'"
},
{
"path": "fixtures/with-dynamic-import/__snapshots__",
"chars": 1462,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`snapshots fixtures/with-dynamic-import: build 1`] = `\nArray [\n \"ch"
},
{
"path": "fixtures/with-dynamic-import/index.html",
"chars": 109,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n <script type=\"module\" src=\"/main.js\"></script>\n </body>\n</html>\n"
},
{
"path": "fixtures/with-dynamic-import/main.js",
"chars": 202,
"preview": "\nimport('./text').then(({ text }) => {\n var node = document.createElement('LI')\n var textNode = document.createTex"
},
{
"path": "fixtures/with-dynamic-import/text.js",
"chars": 27,
"preview": "export const text = 'CIAO!'"
},
{
"path": "fixtures/with-env-plugin/__mirror__/index.html",
"chars": 201,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n<script type=\"module\" src=\"/_hmr_client.js?namespace=hmr-client\"></script>\n\n <s"
},
{
"path": "fixtures/with-env-plugin/__mirror__/main.tsx",
"chars": 824,
"preview": "const node = document.createElement(\"pre\");\nnode.appendChild(document.createTextNode(process.env.SOME_VAR));\ndocument.bo"
},
{
"path": "fixtures/with-env-plugin/__snapshots__",
"chars": 1106,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`snapshots fixtures/with-env-plugin: build 1`] = `\nArray [\n \"index."
},
{
"path": "fixtures/with-env-plugin/bundless.config.js",
"chars": 243,
"preview": "const path = require('path')\nconst { EnvPlugin } = require('@bundless/cli/dist/plugins')\n\nmodule.exports = {\n build: "
},
{
"path": "fixtures/with-env-plugin/envfile",
"chars": 15,
"preview": "SOME_VAR=\"ciao\""
},
{
"path": "fixtures/with-env-plugin/index.html",
"chars": 110,
"preview": "<!DOCTYPE html>\n<html>\n <body>\n <script type=\"module\" src=\"/main.tsx\"></script>\n </body>\n</html>\n"
}
]
// ... and 222 more files (download for full content)
About this extraction
This page contains the full source code of the remorses/bundless GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 422 files (1.0 MB), approximately 404.5k tokens, and a symbol index with 332 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.