├── .nvmrc ├── .prettierignore ├── .npmignore ├── packages ├── icons │ ├── src │ │ ├── browser.js │ │ ├── Icons.js │ │ └── index.js │ ├── README.md │ ├── package.json │ └── scripts │ │ └── build.mjs ├── website │ ├── routes │ │ ├── robots.txt │ │ ├── og-bg.png │ │ ├── og-default.png │ │ ├── roboto-flex.woff2 │ │ ├── roboto-mono.woff2 │ │ ├── introducing-firebolt.png │ │ ├── docs │ │ │ ├── deployment.mdx │ │ │ ├── metadata.mdx │ │ │ ├── ref │ │ │ │ ├── link.mdx │ │ │ │ ├── cls.mdx │ │ │ │ ├── css.mdx │ │ │ │ ├── env.mdx │ │ │ │ ├── useRoute.mdx │ │ │ │ ├── page-mdx.mdx │ │ │ │ ├── layout.mdx │ │ │ │ ├── page-js.mdx │ │ │ │ ├── useCookie.mdx │ │ │ │ ├── handler-js.mdx │ │ │ │ ├── error-boundary.mdx │ │ │ │ ├── useCache.mdx │ │ │ │ ├── useAction.mdx │ │ │ │ ├── context.mdx │ │ │ │ ├── useLoader.mdx │ │ │ │ └── config.mdx │ │ │ ├── cookies.mdx │ │ │ ├── document.mdx │ │ │ ├── index.mdx │ │ │ ├── pkg │ │ │ │ ├── icons.mdx │ │ │ │ ├── cors.mdx │ │ │ │ └── snap.mdx │ │ │ ├── actions.mdx │ │ │ ├── loaders.mdx │ │ │ ├── styles.mdx │ │ │ ├── routes.mdx │ │ │ └── _layout.js │ │ ├── blog │ │ │ ├── _layout.js │ │ │ ├── introducing-firebolt.mdx │ │ │ └── index.js │ │ ├── icon.svg │ │ ├── not-found.js │ │ ├── _layout.js │ │ └── og.js │ ├── .dockerignore │ ├── .gitignore │ ├── README.md │ ├── .env.example │ ├── jsconfig.json │ ├── components │ │ ├── Analytics.js │ │ ├── Image.js │ │ ├── LogoX.js │ │ ├── Meta.js │ │ ├── LogoGithub.js │ │ ├── Page.js │ │ ├── ThemeBtn.js │ │ ├── MobileMenu.js │ │ ├── Header.js │ │ ├── Logo.js │ │ └── Styles.js │ ├── fly.toml │ ├── package.json │ ├── firebolt.config.js │ └── Dockerfile ├── css │ ├── README.md │ ├── package.json │ ├── scripts │ │ └── build.mjs │ └── src │ │ ├── hashString.js │ │ └── index.js ├── cors │ ├── README.md │ ├── package.json │ ├── scripts │ │ └── build.mjs │ └── src │ │ └── index.js ├── snap │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.js │ │ └── hashString.js │ └── scripts │ │ └── build.mjs ├── create-firebolt │ ├── template │ │ ├── firebolt.config.js │ │ ├── _.gitignore │ │ ├── jsconfig.json │ │ ├── routes │ │ │ ├── favicon.ico │ │ │ ├── _layout.js │ │ │ ├── not-found.js │ │ │ ├── index.js │ │ │ └── logo.svg │ │ └── package.json │ ├── README.md │ ├── package.json │ ├── scripts │ │ └── build.mjs │ └── src │ │ └── index.js ├── jsx │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.js │ └── scripts │ │ └── build.mjs └── firebolt │ ├── README.md │ ├── extras │ ├── source-map-support.js │ ├── bootstrap.js │ ├── uuid.js │ ├── config.js │ ├── hashString.js │ ├── matcher.js │ ├── cookies.js │ └── context.js │ ├── src │ ├── utils │ │ ├── reimport.js │ │ ├── pkg.js │ │ ├── style.js │ │ ├── log.js │ │ ├── virtualModule.js │ │ ├── Pending.js │ │ ├── markdownLoader.js │ │ ├── zombieImportPlugin.js │ │ ├── getFilePaths.js │ │ ├── createRoutePattern.js │ │ ├── hashString.js │ │ ├── matcher.js │ │ ├── web.js │ │ ├── workerPlugin.js │ │ ├── errors.js │ │ ├── mdx.js │ │ └── registryPlugin.js │ └── index.js │ ├── package.json │ ├── scripts │ └── build.mjs │ └── types │ └── firebolt.d.ts ├── README.md ├── .gitignore ├── .prettierrc ├── package.json ├── LICENSE └── scripts └── publish.mjs /.nvmrc: -------------------------------------------------------------------------------- 1 | 20.11.0 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | scripts 3 | src 4 | node_modules -------------------------------------------------------------------------------- /packages/icons/src/browser.js: -------------------------------------------------------------------------------- 1 | export { Icons } from './Icons' 2 | -------------------------------------------------------------------------------- /packages/website/routes/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /packages/css/README.md: -------------------------------------------------------------------------------- 1 | # Firebolt CSS 2 | 3 | CSS-in-JS for Firebolt 4 | -------------------------------------------------------------------------------- /packages/cors/README.md: -------------------------------------------------------------------------------- 1 | # Firebolt CORS 2 | 3 | CORS plugin for Firebolt 4 | -------------------------------------------------------------------------------- /packages/icons/README.md: -------------------------------------------------------------------------------- 1 | # Firebolt CSS 2 | 3 | CSS-in-JS for Firebolt 4 | -------------------------------------------------------------------------------- /packages/snap/README.md: -------------------------------------------------------------------------------- 1 | # Firebolt CSS 2 | 3 | CSS-in-JS for Firebolt 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Firebolt 2 | 3 | See [firebolt.dev](https://firebolt.dev) for documentation. 4 | -------------------------------------------------------------------------------- /packages/website/.dockerignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .firebolt 3 | .env* 4 | !.env.example 5 | node_modules -------------------------------------------------------------------------------- /packages/website/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .firebolt 3 | .env* 4 | !.env.example 5 | node_modules -------------------------------------------------------------------------------- /packages/create-firebolt/template/firebolt.config.js: -------------------------------------------------------------------------------- 1 | export const config = { 2 | // your config 3 | } 4 | -------------------------------------------------------------------------------- /packages/jsx/README.md: -------------------------------------------------------------------------------- 1 | # Firebolt JSX 2 | 3 | Custom JSX bindings for Firebolt to power our CSS-in-JS 4 | -------------------------------------------------------------------------------- /packages/website/README.md: -------------------------------------------------------------------------------- 1 | # Firebolt Website 2 | 3 | The [firebolt.dev](https://firebolt.dev) website. 4 | -------------------------------------------------------------------------------- /packages/create-firebolt/template/_.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .firebolt 3 | .env* 4 | !.env.example 5 | node_modules -------------------------------------------------------------------------------- /packages/firebolt/README.md: -------------------------------------------------------------------------------- 1 | # Firebolt 2 | 3 | See [firebolt.dev](https://firebolt.dev) for documentation 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .firebolt 3 | .secret 4 | .env* 5 | !.env.example 6 | node_modules 7 | arch 8 | dist 9 | *.db -------------------------------------------------------------------------------- /packages/website/routes/og-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebolt-dev/firebolt/HEAD/packages/website/routes/og-bg.png -------------------------------------------------------------------------------- /packages/website/.env.example: -------------------------------------------------------------------------------- 1 | # Port to run the app on 2 | PORT=3000 3 | 4 | # Base URL 5 | PUBLIC_DOMAIN=http://localhost:3000 -------------------------------------------------------------------------------- /packages/firebolt/extras/source-map-support.js: -------------------------------------------------------------------------------- 1 | import sourceMapSupport from 'source-map-support' 2 | sourceMapSupport.install() 3 | -------------------------------------------------------------------------------- /packages/website/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./*"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/website/routes/og-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebolt-dev/firebolt/HEAD/packages/website/routes/og-default.png -------------------------------------------------------------------------------- /packages/website/routes/roboto-flex.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebolt-dev/firebolt/HEAD/packages/website/routes/roboto-flex.woff2 -------------------------------------------------------------------------------- /packages/website/routes/roboto-mono.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebolt-dev/firebolt/HEAD/packages/website/routes/roboto-mono.woff2 -------------------------------------------------------------------------------- /packages/create-firebolt/template/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./*"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/website/routes/introducing-firebolt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebolt-dev/firebolt/HEAD/packages/website/routes/introducing-firebolt.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "jsxSingleQuote": true, 5 | "trailingComma": "es5", 6 | "arrowParens": "avoid" 7 | } 8 | -------------------------------------------------------------------------------- /packages/create-firebolt/template/routes/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebolt-dev/firebolt/HEAD/packages/create-firebolt/template/routes/favicon.ico -------------------------------------------------------------------------------- /packages/create-firebolt/README.md: -------------------------------------------------------------------------------- 1 | # Create Firebolt 2 | 3 | The easiest way to get started with Firebolt is to use `create-firebolt`. 4 | 5 | See [firebolt.dev](https://firebolt.dev) for documentation. 6 | -------------------------------------------------------------------------------- /packages/firebolt/src/utils/reimport.js: -------------------------------------------------------------------------------- 1 | // utility to re-import a module 2 | export function reimport(module) { 3 | // delete require.cache[module] 4 | return import(`${module}?v=${Date.now()}`) 5 | } 6 | -------------------------------------------------------------------------------- /packages/firebolt/src/utils/pkg.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra' 2 | import path from 'path' 3 | 4 | const modDir = new URL('.', import.meta.url).pathname 5 | const pkgFile = path.join(modDir, '../package.json') 6 | 7 | export const pkg = await fs.readJSON(pkgFile) 8 | -------------------------------------------------------------------------------- /packages/cors/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@firebolt-dev/cors", 3 | "version": "0.4.3", 4 | "main": "dist/index.js", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "node scripts/build.mjs", 8 | "build": "node scripts/build.mjs --production" 9 | }, 10 | "dependencies": {} 11 | } 12 | -------------------------------------------------------------------------------- /packages/website/components/Analytics.js: -------------------------------------------------------------------------------- 1 | export function Analytics() { 2 | if (process.env.NODE_ENV === 'production') { 3 | return ( 4 | 124 | `) 125 | } 126 | if (change.type === 'remove') { 127 | let { key } = change 128 | inserts.write(` 129 | 130 | `) 131 | } 132 | } 133 | changes.length = 0 134 | }, 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /packages/website/routes/docs/routes.mdx: -------------------------------------------------------------------------------- 1 | 5 | 6 | # Routes 7 | 8 | The `routes` directory is an intuitive yet unique way of organizing all of your static and dynamic content together in a single folder. 9 | 10 | ## Pages 11 | 12 | You can add a page to your app by creating a `.js` file that exports a default React component 13 | 14 | ```jsx title='routes/about.js' lineNumbers 15 | export default function About() { 16 | return ( 17 |
18 |

About Us

19 |

...

20 |
21 | ) 22 | } 23 | ``` 24 | 25 | This page is rendered when you visit `/about`. 26 | 27 | Pages are just regular React components with no magical named exports. You'll learn more about how to fetch and interact with data later on in this guide. 28 | 29 | ## Dynamic Pages 30 | 31 | You can create dynamic route segments by using a `$` variable in your file or folder name: 32 | 33 | ```jsx title='routes/blog/$slug.js' lineNumbers 34 | import { useRoute } from 'firebolt' 35 | 36 | export default function Post() { 37 | const { slug } = useRoute().params 38 | return
Slug: {slug}
39 | } 40 | ``` 41 | 42 | You can learn more about these and catch-all segments on the [page.js](/docs/ref/page-js) reference. 43 | 44 | ## Layouts 45 | 46 | Firebolt also supports nested layouts that maintain their state when switching between pages that share the same layout. 47 | 48 | ```jsx title='routes/docs/_layout.js' lineNumbers 49 | export default function Docs({ children }) { 50 | return ( 51 |
52 | 53 | {children} 54 |
55 | ) 56 | } 57 | ``` 58 | 59 | ```md title='routes/docs/intro.mdx' lineNumbers 60 | # Intro 61 | 62 | Let's get started... 63 | ``` 64 | 65 | In the example above, visiting `/docs/intro` will show the intro page nested inside the docs layout. 66 | 67 | The root layout at `routes/_layout.js` is used to wrap your entire app with `` and `` tags. 68 | 69 | ## MDX 70 | 71 | As you might have noticed we also support MDX pages out of the box, allowing you to write your content with super powered markdown. Simply use the `.mdx` extension. 72 | 73 | You can [configure](/docs/ref/config) MDX with plugins to provide syntax highlighting or support github flavored markdown, anything you need. 74 | 75 | You can also customize the components rendered by MDX with an [MDXProvider](/docs/ref/page-mdx) 76 | 77 | ## Static Files 78 | 79 | The `routes` directory also serves all your static assets such as fonts and images. For example `routes/robots.txt` will be available at `/robots.txt`. 80 | 81 | ## Virtual Files 82 | 83 | For dynamic files that need to be generated on the fly you can create virtual assets by giving them a `.js` extension and handling the appropriate HTTP method 84 | 85 | ```jsx title='routes/robots.txt.js' lineNumbers 86 | export function get(ctx) { 87 | const data = ` 88 | User-agent: * 89 | Disallow: 90 | ` 91 | return new Response(data, { 92 | headers: { 93 | 'Content-Type': 'text/plain', 94 | }, 95 | }) 96 | } 97 | ``` 98 | 99 | This gives you full control over all of your content and can be useful for many things including sitemap generation, image transformation and open graph image generators. 100 | 101 | ## API Routes 102 | 103 | You can use the same mechanism to create API endpoints, eg `GET /api/users`: 104 | 105 | ```jsx title='routes/api/users.js' lineNumbers 106 | export function get(ctx) { 107 | return await ctx.db('users') 108 | } 109 | ``` 110 | 111 | As you'll see later on, unless your app explicitly needs to provide an API to its users, you won't even need these. 112 | 113 | ## Summary 114 | 115 | The `routes` folder is a simple yet powerful concept that allows you to keep your app organized, supporting essentially anything you might need. 116 | 117 | When building components that need to be shared across multiple routes, you can place them outside of the `routes` directory in a `components` directory or similar, and pull them in as needed. 118 | 119 | By the end of this guide you'll see that it's possible to build large complex SaaS applications that have little more than a `routes` and `components` directory. It's magic. 120 | -------------------------------------------------------------------------------- /packages/create-firebolt/template/routes/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/firebolt/src/utils/mdx.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a modified version of @mdx-js/esbuild 3 | * See: https://github.com/mdx-js/mdx/blob/main/packages/esbuild/lib/index.js 4 | * 5 | * Mainly this plugin employs better caching, with the caveat that we have to manually 6 | * evict them in the file watcher in exec.js 7 | */ 8 | 9 | import assert from 'node:assert' 10 | import fs from 'node:fs/promises' 11 | import path from 'node:path' 12 | import { createFormatAwareProcessors } from '@mdx-js/mdx/internal-create-format-aware-processors' 13 | import { extnamesToRegex } from '@mdx-js/mdx/internal-extnames-to-regex' 14 | import { VFile } from 'vfile' 15 | import { VFileMessage } from 'vfile-message' 16 | 17 | const eol = /\r\n|\r|\n|\u2028|\u2029/g 18 | 19 | const name = '@mdx-js/esbuild' 20 | 21 | export default function createMdx(options) { 22 | const { extnames, process } = createFormatAwareProcessors(options || {}) 23 | 24 | const cache = {} 25 | 26 | return { 27 | plugin: { 28 | name, 29 | setup, 30 | }, 31 | evict, 32 | } 33 | 34 | function setup(build) { 35 | build.onLoad({ filter: extnamesToRegex(extnames) }, onload) 36 | 37 | async function onload(data) { 38 | if (cache[data.path]) { 39 | return cache[data.path] 40 | } 41 | 42 | let document = String( 43 | data.pluginData && 44 | data.pluginData.contents !== null && 45 | data.pluginData.contents !== undefined 46 | ? data.pluginData.contents 47 | : await fs.readFile(data.path) 48 | ) 49 | 50 | const head = ` 51 | import { cloneElement } from 'react' 52 | import { useMDXComponents } from 'firebolt' 53 | 54 | ` 55 | 56 | const foot = ` 57 | export default function MDXWrapper({ children }) { 58 | const components = useMDXComponents() 59 | return cloneElement(children, { components }) 60 | }` 61 | 62 | document = head + document + foot 63 | 64 | const state = { doc: document, name, path: data.path } 65 | let file = new VFile({ path: data.path, value: document }) 66 | let value 67 | let messages = [] 68 | const errors = [] 69 | const warnings = [] 70 | 71 | try { 72 | file = await process(file) 73 | value = file.value 74 | messages = file.messages 75 | } catch (error_) { 76 | const cause = /** @type {VFileMessage | Error} */ (error_) 77 | const message = 78 | 'reason' in cause 79 | ? cause 80 | : new VFileMessage('Cannot process MDX file with esbuild', { 81 | cause, 82 | ruleId: 'process-error', 83 | source: '@mdx-js/esbuild', 84 | }) 85 | message.fatal = true 86 | messages.push(message) 87 | } 88 | 89 | for (const message of messages) { 90 | const list = message.fatal ? errors : warnings 91 | list.push(vfileMessageToEsbuild(state, message)) 92 | } 93 | 94 | // Safety check: the file has a path, so there has to be a `dirname`. 95 | assert(file.dirname, 'expected `dirname` to be defined') 96 | 97 | const result = { 98 | contents: value || '', 99 | errors, 100 | resolveDir: path.resolve(file.cwd, file.dirname), 101 | warnings, 102 | } 103 | 104 | cache[data.path] = result 105 | 106 | return result 107 | } 108 | } 109 | 110 | function evict(path) { 111 | console.log('evict', path, !!cache[path]) 112 | delete cache[path] 113 | } 114 | } 115 | 116 | function vfileMessageToEsbuild(state, message) { 117 | const place = message.place 118 | const start = place ? ('start' in place ? place.start : place) : undefined 119 | const end = place && 'end' in place ? place.end : undefined 120 | let length = 0 121 | let lineStart = 0 122 | let line = 0 123 | let column = 0 124 | 125 | if (start && start.offset !== undefined) { 126 | line = start.line 127 | column = start.column - 1 128 | lineStart = start.offset - column 129 | length = 1 130 | 131 | if (end && end.offset !== undefined) { 132 | length = end.offset - start.offset 133 | } 134 | } 135 | 136 | eol.lastIndex = lineStart 137 | 138 | const match = eol.exec(state.doc) 139 | const lineEnd = match ? match.index : state.doc.length 140 | 141 | return { 142 | detail: message, 143 | id: '', 144 | location: { 145 | column, 146 | file: state.path, 147 | length: Math.min(length, lineEnd), 148 | line, 149 | lineText: state.doc.slice(lineStart, lineEnd), 150 | namespace: 'file', 151 | suggestion: '', 152 | }, 153 | notes: [], 154 | pluginName: state.name, 155 | text: message.reason, 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /packages/website/components/Logo.js: -------------------------------------------------------------------------------- 1 | export function Logo(props) { 2 | return ( 3 | 11 | 15 | 16 | 20 | 24 | 28 | 32 | 36 | 40 | 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /packages/website/routes/docs/ref/config.mdx: -------------------------------------------------------------------------------- 1 | 5 | 6 | # firebolt.config.js 7 | 8 | The Firebolt config file allows you to configure how your app runs. 9 | 10 | ```jsx title='firebolt.config.js' lineNumbers 11 | export const config = { 12 | // your config here 13 | } 14 | ``` 15 | 16 | ## Port 17 | 18 | By default your app will attempt to use the `PORT` environment variable, otherwise it will use the port in your config file which defaults to `3000`. 19 | 20 | ```jsx title='firebolt.config.js' lineNumbers 21 | export const config = { 22 | port: 4000, 23 | } 24 | ``` 25 | 26 | ## Source Maps 27 | 28 | Production builds don't include source maps in client bundles by default, but you can enable them with this setting if needed: 29 | 30 | ```jsx title='firebolt.config.js' lineNumbers 31 | export const config = { 32 | productionBrowserSourceMaps: true, 33 | } 34 | ``` 35 | 36 | ## Context 37 | 38 | You can decorate the [Context](/docs/ref/context) object that is passed to middleware, [loaders](/docs/loaders), [actions](/docs/actions) and [handlers](/docs/ref/handler.js) 39 | 40 | ```jsx title='firebolt.config.js' lineNumbers 41 | import db from './db' 42 | 43 | export const config = { 44 | context: { 45 | db, 46 | }, 47 | } 48 | ``` 49 | 50 | In this example, all Context objects will be provided a `ctx.db` value 51 | 52 | ## MDX Plugins 53 | 54 | Firebolt uses [MDX](https://mdxjs.com/) and you can configure plugins for it here: 55 | 56 | ```jsx title='firebolt.config.js' lineNumbers 57 | import remarkGFM from 'remark-gfm' 58 | import rehypeShiki from '@shikijs/rehype' 59 | 60 | export const config = { 61 | mdx: { 62 | remarkPlugins: [remarkGFM], 63 | rehypePlugins: [rehypeShiki], 64 | }, 65 | } 66 | ``` 67 | 68 | ## Cookie Defaults 69 | 70 | By default, if you don't provide options when setting cookies they will expire after the current session. 71 | 72 | It can be useful to set your cookie defaults globally so that you don't have to provide options repeatedly throughout your app. 73 | 74 | ```jsx title='firebolt.config.js' lineNumbers 75 | export const config = { 76 | cookie: { 77 | expires: 30, // expire in 30 days 78 | }, 79 | } 80 | ``` 81 | 82 | See [useCookie](/docs/ref/useCookie) for all available options. 83 | 84 | ## Public Env Prefix 85 | 86 | By default any environment variables with the `PUBLIC_` prefix will be available on the client. You can change this prefix if needed. 87 | 88 | ```jsx title='firebolt.config.js' lineNumbers 89 | export const config = { 90 | publicEnvPrefix: 'ANOTHER_PREFIX_', 91 | } 92 | ``` 93 | 94 | Learn more about environment variables on the [.env](/docs/ref/env) page. 95 | 96 | ## Build 97 | 98 | The build function can be used to do any kind of work at build time, such as generating sitemaps or icons. 99 | 100 | ```jsx title='firebolt.config.js' lineNumbers 101 | export const config = { 102 | async build() { 103 | // ... 104 | }, 105 | } 106 | ``` 107 | 108 | The function is called just once when running `npm run dev` or `npm run build`. 109 | 110 | ## Start 111 | 112 | The start function is run each time your app starts. 113 | 114 | ```jsx title='firebolt.config.js' lineNumbers 115 | export const config = { 116 | async start() { 117 | // ... 118 | }, 119 | } 120 | ``` 121 | 122 | The function is called just once when running `npm run dev` or `npm run start`. 123 | 124 | ## Middleware 125 | 126 | Use middleware to intercept requests and handle them as needed 127 | 128 | ```jsx title='firebolt.config.js' lineNumbers 129 | export const config = { 130 | middleware: [ 131 | (ctx) => { 132 | // example middleware that tags all requests with an ID 133 | ctx.headers.set('X-Request-ID') = ctx.uuid() 134 | }, 135 | (ctx) => { 136 | // example middleware that intercepts /foobars and returns some text 137 | if (ctx.req.pathname === '/foobars') { 138 | return new Response('foo-bars', { 139 | headers: { 140 | 'Content-Type': 'text/plain', 141 | }, 142 | }) 143 | } 144 | }, 145 | ], 146 | } 147 | ``` 148 | 149 | Middleware is provided a [Context](/docs/ref/context) object that provides the Web API [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) and utilities to manage cookies, headers and custom values (such as a database) that you configured in your [firebolt.config.js](/docs/ref/config). 150 | 151 | ## Plugins 152 | 153 | Plugins augment your config to intercept and handle requests among other things. 154 | 155 | ```jsx title='firebolt.config.js' lineNumbers 156 | import icons from '@firebolt-dev/icons' 157 | 158 | export const config = { 159 | plugins: [icons()], 160 | } 161 | ``` 162 | 163 | The `@firebolt-dev/icons` plugin generates all of your favicons and icons from a single `svg` and injects them into your ``. 164 | 165 | {/* Learn more in the [Plugins](/docs/guides/plugins) guide. */} 166 | -------------------------------------------------------------------------------- /packages/firebolt/src/utils/registryPlugin.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra' 2 | import path from 'path' 3 | import { hashString } from './hashString' 4 | 5 | const loaderActionRegex = /(useAction|useLoader)/ 6 | 7 | export function registryPlugin({ registry, appDir }) { 8 | return { 9 | name: 'registryPlugin', 10 | setup(build) { 11 | build.onLoad({ filter: /\.js$/ }, async args => { 12 | const modPath = args.path 13 | 14 | // skip node_modules 15 | if (modPath.includes('node_modules')) { 16 | return 17 | } 18 | 19 | // skip .firebolt build folder 20 | if (modPath.includes('.firebolt')) { 21 | return 22 | } 23 | 24 | // read file contents 25 | let contents = await fs.readFile(modPath, 'utf8') 26 | 27 | // early exit if no useLoader/useAction hooks 28 | if (!loaderActionRegex.test(contents)) { 29 | return { contents, loader: 'jsx' } 30 | } 31 | 32 | // console.log('---') 33 | // console.log(modPath) 34 | 35 | const imports = {} 36 | const exports = {} 37 | 38 | let matches 39 | 40 | // get all named imports 41 | const namedImportRegex = /import\s+{([^}]+)}\s+from\s+(['"][^'"]+['"])/g 42 | while ((matches = namedImportRegex.exec(contents)) !== null) { 43 | const file = matches[2].slice(1).slice(0, -1) 44 | const bits = matches[1].split(',') 45 | bits.forEach(bit => { 46 | const [name, alias] = bit.trim().split(/\s+as\s+/) 47 | const usedName = alias || name 48 | imports[usedName.trim()] = { 49 | name: name.trim(), 50 | alias: alias?.trim() || null, 51 | file, 52 | } 53 | }) 54 | } 55 | 56 | // get all default imports 57 | const defaultImportRegex = /import\s+(\w+)\s+from\s+(['"][^'"]+['"])/g 58 | while ((matches = defaultImportRegex.exec(contents)) !== null) { 59 | const file = matches[2].slice(1).slice(0, -1) 60 | const name = matches[1].trim() 61 | imports[name] = { 62 | name, 63 | alias: null, 64 | file, 65 | } 66 | } 67 | 68 | // get all exports 69 | const exportsRegex = /export\s+(async\s+function|function|const)\s+(\w+)/g // prettier-ignore 70 | while ((matches = exportsRegex.exec(contents)) !== null) { 71 | const name = matches[2].trim() 72 | exports[name] = { 73 | name, 74 | alias: null, 75 | file: modPath, 76 | } 77 | } 78 | 79 | // console.log('imports', imports) 80 | // console.log('exports', exports) 81 | 82 | const getId = token => { 83 | if (imports[token]) { 84 | const name = imports[token].name 85 | const alias = imports[token].alias 86 | const file = path.resolve(path.dirname(modPath), imports[token].file) // prettier-ignore 87 | const relFile = path.relative(appDir, file) 88 | const fn = alias || name 89 | const id = `f_${hashString(relFile + fn)}` 90 | if (registry) { 91 | registry.set(id, { 92 | id, 93 | file, 94 | name, 95 | }) 96 | } 97 | return id 98 | // fnInfo.push({ name, alias, file, id }) 99 | } 100 | if (exports[token]) { 101 | const name = token 102 | const file = modPath 103 | const relFile = path.relative(appDir, file) 104 | const fn = name 105 | const id = `f_${hashString(relFile + fn)}` 106 | if (registry) { 107 | registry.set(id, { 108 | id, 109 | file, 110 | name, 111 | }) 112 | } 113 | return id 114 | } 115 | } 116 | 117 | // transform useLoader & useAction calls 118 | const hookRegex = /(useLoader|useAction)\(([^,)]+)/g 119 | const lines = contents.split('\n') 120 | // let inBlockComment = false 121 | const result = lines.map(line => { 122 | // // track and skip block comments 123 | // if (line.includes('/*')) inBlockComment = true 124 | // if (line.includes('*/')) { 125 | // inBlockComment = false 126 | // return line 127 | // } 128 | // // ignore inline comments// Ignore lines that are within block comments or are inline comments 129 | // if (inBlockComment || line.trim().startsWith('//')) return line 130 | // find and replace hook calls outside comments 131 | return line.replace(hookRegex, (match, hook, fnName) => { 132 | // match function to import/export id 133 | const id = getId(fnName) 134 | // ignore if none 135 | if (!id) return match 136 | // replace with id 137 | // console.log('replace', fnName, id) 138 | const replacement = `${hook}('${id}'` 139 | if (match.trim().endsWith(')')) { 140 | return ( 141 | replacement + 142 | match.substring( 143 | match.indexOf(fnName) + fnName.length, 144 | match.length - 1 145 | ) + 146 | ')' 147 | ) 148 | } 149 | return replacement 150 | }) 151 | }) 152 | 153 | contents = result.join('\n') 154 | 155 | // console.log('registry', registry) 156 | 157 | return { contents, loader: 'jsx' } 158 | }) 159 | }, 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /packages/website/components/Styles.js: -------------------------------------------------------------------------------- 1 | import { useCookie, css } from 'firebolt' 2 | 3 | export function Styles() { 4 | return ( 5 | <> 6 | 7 | 8 |
9 | 10 | ) 11 | } 12 | 13 | function Reset() { 14 | // based on https://www.digitalocean.com/community/tutorials/css-minimal-css-reset 15 | return ( 16 |