├── .github ├── logo.svg └── workflows │ └── node.js.yml ├── .gitignore ├── CHANGELOG.md ├── README.md ├── fragments ├── auth │ ├── blueprint.js │ └── template │ │ └── src │ │ ├── components │ │ └── Auth │ │ │ ├── Link.svelte │ │ │ ├── Login.svelte │ │ │ └── store.js │ │ └── pages │ │ ├── _layout.svelte.fragment.js │ │ ├── login.svelte │ │ ├── protected │ │ ├── _reset.svelte │ │ └── index.svelte │ │ └── user.svelte ├── autoPreprocess │ ├── blueprint.js │ └── package.json ├── base │ ├── blueprint.js │ ├── package.json │ └── template │ │ ├── .gitignore │ │ ├── src │ │ ├── App.svelte │ │ ├── App.svelte.fragment.js │ │ ├── main.js │ │ └── pages │ │ │ ├── _fallback.svelte │ │ │ ├── _layout.svelte │ │ │ ├── guide │ │ │ ├── _layout.svelte │ │ │ ├── index.svelte │ │ │ └── routify.md │ │ │ └── index.svelte │ │ ├── static │ │ ├── __app.html │ │ └── favicon.ico │ │ └── test │ │ ├── build │ │ └── README.md │ │ ├── common │ │ ├── README.md │ │ └── base.test.js │ │ ├── dev │ │ └── README.md │ │ └── lib │ │ ├── index.js │ │ └── wrapper.js ├── content │ ├── blueprint.js │ └── template │ │ ├── src │ │ └── pages │ │ │ ├── introduction │ │ │ ├── _layout.svelte │ │ │ └── index.svelte │ │ │ └── posts │ │ │ ├── [slug].svelte │ │ │ ├── _layout.svelte │ │ │ ├── entries │ │ │ ├── _layout.svelte │ │ │ ├── html-ftw.svelte │ │ │ ├── secretpost.svelte │ │ │ └── something-about-html.svelte │ │ │ ├── index.svelte │ │ │ └── inlined.svelte │ │ └── test │ │ └── common │ │ └── content.test.js ├── dev │ └── blueprint.js ├── docker-nginx │ ├── blueprint.js │ └── template │ │ └── Dockerfile ├── docker-ssr │ ├── blueprint.js │ └── template │ │ ├── Dockerfile │ │ └── test │ │ └── build │ │ └── docker-ssr.test.js ├── i18n │ ├── blueprint.js │ └── template │ │ ├── src │ │ ├── App.svelte.fragment.js │ │ ├── components │ │ │ └── Lang.svelte │ │ └── pages │ │ │ ├── _layout.svelte.fragment.js │ │ │ └── guide │ │ │ └── i18n.svelte │ │ └── test │ │ └── common │ │ └── i18n.test.js ├── markdown │ ├── blueprint.js │ ├── package.json │ └── template │ │ ├── src │ │ ├── components │ │ │ └── Card.svelte │ │ └── pages │ │ │ └── guide │ │ │ └── markdown.md │ │ └── test │ │ └── common │ │ └── mdsvex.test.js ├── milligram │ ├── blueprint.js │ └── template │ │ ├── src │ │ └── pages │ │ │ └── guide │ │ │ └── milligram.svelte │ │ └── static │ │ └── __app.html.fragment.js ├── mobile │ ├── blueprint.js │ └── template │ │ ├── src │ │ ├── components │ │ │ ├── Decorator.svelte │ │ │ └── navigation │ │ │ │ ├── Navbar.svelte │ │ │ │ └── Overlay.svelte │ │ └── pages │ │ │ ├── _layout.svelte │ │ │ ├── guide │ │ │ └── mobile.svelte │ │ │ ├── home │ │ │ ├── _layout.svelte │ │ │ └── index.svelte │ │ │ └── index.svelte │ │ └── static │ │ ├── __app.html.fragment.js │ │ └── mobile.css ├── navigation │ ├── blueprint.js │ └── template │ │ ├── src │ │ ├── components │ │ │ └── Navbar │ │ │ │ ├── Bar.svelte │ │ │ │ └── Navigation.svelte │ │ └── pages │ │ │ └── _layout.svelte │ │ └── test │ │ └── common │ │ └── navigation.test.js ├── nollup │ ├── blueprint.js │ └── template │ │ ├── .nolluprc.js │ │ ├── .nolluprc.js.fragment.js │ │ ├── package.json │ │ └── rollup.config.js.fragment.js ├── postcss │ ├── blueprint.js │ └── package.json ├── rollup │ ├── blueprint.js │ └── template │ │ ├── package.json │ │ ├── rollup.config.js │ │ ├── rollup.config.js.fragment.js │ │ ├── src │ │ └── pages │ │ │ └── guide │ │ │ └── rollup.svelte │ │ └── static │ │ └── __app.html.fragment.js ├── scroll-navigation │ ├── blueprint.js │ └── template │ │ └── src │ │ └── pages │ │ ├── _layout.svelte │ │ ├── frontpage │ │ ├── _layout.svelte │ │ ├── contact.svelte │ │ ├── features.svelte │ │ ├── home.svelte │ │ └── story.svelte │ │ ├── guide │ │ └── single-page.svelte │ │ └── index.svelte ├── serviceworker │ ├── blueprint.js │ └── template │ │ ├── src │ │ ├── App.svelte.fragment.js │ │ ├── components │ │ │ └── Serviceworker.svelte │ │ ├── pages │ │ │ └── guide │ │ │ │ └── serviceworker.md │ │ └── sw.js │ │ ├── static │ │ ├── images │ │ │ └── touch-icons │ │ │ │ ├── logo-192.png │ │ │ │ └── logo-800.png │ │ └── manifest.json │ │ └── workbox-config.js ├── snowpack │ ├── blueprint.js │ ├── package.json │ └── template │ │ ├── snowpack.config.js │ │ ├── src │ │ └── pages │ │ │ └── guide │ │ │ └── snowpack.md │ │ └── static │ │ └── __app.html.fragment.js ├── spank │ ├── blueprint.js │ ├── package.json │ └── template │ │ ├── src │ │ └── pages │ │ │ └── guide │ │ │ └── spank.md │ │ └── test │ │ └── build │ │ └── spank.test.js ├── tailwindcss │ ├── blueprint.js │ ├── package.json │ └── template │ │ ├── .browserslistrc │ │ └── src │ │ ├── App.svelte.fragment.js │ │ └── pages │ │ └── guide │ │ └── tailwind.svelte ├── vercel │ ├── blueprint.js │ └── template │ │ ├── api │ │ └── vercel-ssr │ │ │ ├── build.js │ │ │ ├── index.js │ │ │ └── package.json │ │ └── vercel.json ├── vite │ ├── blueprint.js │ ├── package.json │ └── template │ │ ├── src │ │ └── pages │ │ │ └── guide │ │ │ └── vite.md │ │ └── static │ │ └── __app.html.fragment.js └── windicss │ ├── blueprint.js │ ├── package.json │ └── template │ └── src │ └── pages │ └── guide │ └── windicss.svelte ├── lib ├── cli.js ├── generate-combos.js └── prompts.js ├── package.json └── test └── index.js /.github/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | pull_request: 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | matrix: 17 | node-version: [15.x] 18 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v2 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - run: npm i 27 | - run: npm test 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/output 3 | temp 4 | test/workspace 5 | .canvasit-temp 6 | templates -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [0.1.8](https://github.com/roxiness/stackmix/compare/v0.1.7...v0.1.8) (2021-12-26) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * spank didn't work with vite ([3848b77](https://github.com/roxiness/stackmix/commit/3848b775cef3df1ebaf77eeb3c2c8645a3750413)) 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | stackmix 3 |

4 | 5 | # Stackmix [preview] 6 | ### Template generator based on Svelte and Routify. 7 | 8 | --- 9 | 10 | Don't be fooled by the "prerelease". You should totally take it for a ride. 😎 11 | 12 | ## Getting started 13 | ``` 14 | npx stackmix [app-name] 15 | cd [app-name] 16 | npm install 17 | npm run dev 18 | ``` 19 | 20 | Remember to report any bugs back to us. 😉 21 | 22 | --- 23 | 24 | ## Getting started [for contributors and testers] 25 | ```bash 26 | $ git clone https://github.com/roxiness/stackmix.git 27 | $ cd stackmix 28 | $ npm install 29 | ``` 30 | 31 | #### To build a template from fragments 32 | `node lib/cli` 33 | 34 | __The wizard can be skipped by providing fragments as arguments__ 35 | `npm run generate -- -fragments rollup,i18n,navigation,content,miligram,mdsvex` 36 | 37 | __use `--watch` to update output on fragment change__ 38 | 39 | --- 40 | 41 | **Given the variety of fragments, we would greatly appreciate contributors for this projects** 🙏 42 | -------------------------------------------------------------------------------- /fragments/auth/blueprint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'feature', 3 | } 4 | -------------------------------------------------------------------------------- /fragments/auth/template/src/components/Auth/Link.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 17 | 18 | 31 | -------------------------------------------------------------------------------- /fragments/auth/template/src/components/Auth/Login.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 |

Login

11 |
12 | 13 | 14 | {#if !$authenticating && !$user} 15 | 16 | {:else} 17 | authenticating... 18 | {/if} 19 |
20 |
21 | -------------------------------------------------------------------------------- /fragments/auth/template/src/components/Auth/store.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store' 2 | export const user = writable(null) 3 | export const authenticating = writable(true) 4 | export const logout = () => { 5 | localStorage.removeItem('user') 6 | authenticate() 7 | } 8 | 9 | export const login = (username, password) => { 10 | authenticating.set(true) 11 | if (username === 'user@example.com' && password === 'pass') { 12 | const user = { username, token: 'abcdefg' } 13 | storeCredentials(user) 14 | // we're delaying authentication to simulate realworld timings 15 | setTimeout(authenticate, 500) 16 | } else { 17 | authenticating.set(false) 18 | console.error('wrong username or password') 19 | } 20 | } 21 | 22 | // we're delaying authentication to simulate realworld timings 23 | setTimeout(authenticate, 500) 24 | 25 | function authenticate() { 26 | /** 27 | * Insecure example! 28 | * In production, a token should be stored in localStorage/cookie 29 | * and sent to an auth server for verification. 30 | * */ 31 | user.set(getCredentials()) 32 | 33 | // we need to inform other components that we're no longer authenticating 34 | authenticating.set(false) 35 | } 36 | 37 | function getCredentials() { 38 | const cred = localStorage.getItem('user') 39 | return cred && JSON.parse(cred) 40 | } 41 | 42 | function storeCredentials(user) { 43 | localStorage.setItem('user', JSON.stringify(user)) 44 | } 45 | -------------------------------------------------------------------------------- /fragments/auth/template/src/pages/_layout.svelte.fragment.js: -------------------------------------------------------------------------------- 1 | module.exports.patch = ({ placeholders, configs }) => { 2 | placeholders.script.push( 3 | `import Link from '../components/Auth/Link.svelte'`, 4 | ) 5 | placeholders.navigation.push(`
6 | 7 |
`) 8 | } 9 | -------------------------------------------------------------------------------- /fragments/auth/template/src/pages/login.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /fragments/auth/template/src/pages/protected/_reset.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 |
19 | Go back 20 |

21 | Protected module {#if $user}{/if} 23 |

24 |
25 | {#if $user} 26 | 27 | {:else if $authenticating} 28 |

authenticating...

29 | {:else} 30 | 31 | {/if} 32 |
33 |
34 | 35 | 54 | -------------------------------------------------------------------------------- /fragments/auth/template/src/pages/protected/index.svelte: -------------------------------------------------------------------------------- 1 |

protected/index.svelte

2 |

3 | By checking for a user in 4 | 5 | _layout.svelte 6 | or 7 | _reset.svelte we can render a conditional login component instead 8 | of the actual page. 9 |

10 |

This ensures that we're not bouncing the user from URL to URL.

11 |

12 | If we need a dedicated login page, we can create a pages/login.svelte 14 | and use it to import our Login component 15 |

16 | -------------------------------------------------------------------------------- /fragments/auth/template/src/pages/user.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | {#if $authenticating} 13 | authenticating... 14 | {:else if $user} 15 |

16 | Hello {$user.username} 17 |

18 | 19 | {/if} 20 | 21 | 22 | -------------------------------------------------------------------------------- /fragments/autoPreprocess/blueprint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'feature', 3 | imports: { 4 | autoPreprocess: ['svelte-preprocess'], 5 | }, 6 | configs: ({ getConfigString, $require }) => ({ 7 | packagejson: require('./package.json'), 8 | svelte: { 9 | preprocess: [ 10 | $require('autoPreprocess')(getConfigString('autoPreprocess')), 11 | ], 12 | }, 13 | autoPreprocess: { 14 | /** config */ 15 | }, 16 | }), 17 | } 18 | -------------------------------------------------------------------------------- /fragments/autoPreprocess/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "svelte-preprocess": "^4.6.3" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /fragments/base/blueprint.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** @type {import('canvasit')['Blueprint']} */ 4 | module.exports = { 5 | type: 'base', 6 | configs: () => ({ 7 | options: { base: { useMarkdown: false } }, 8 | test: {}, 9 | svelte: { 10 | // Extract component CSS — better performance 11 | // css: "css => css.write(`bundle.css`)", 12 | preprocess: [], 13 | }, 14 | packagejson: require('./package.json'), 15 | }), 16 | hooks: { 17 | afterPatch: async ctx => { 18 | ctx.writeTo( 19 | 'package.json', 20 | JSON.stringify(ctx.configs.packagejson, null, 2), 21 | ) 22 | if (!ctx.configs.options.base.useMarkdown) 23 | await ctx.fileWalker(convertMarkdownToSvelte) 24 | }, 25 | }, 26 | } 27 | 28 | function convertMarkdownToSvelte(file) { 29 | if (file.ext === 'md') { 30 | const md = require('markdown-it')({ html: true }) 31 | file.content = md.render(file.content) 32 | file.remove() 33 | file.filepath = file.filepath.replace(/\.[^.]+?$/, '.svelte') 34 | file.save() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /fragments/base/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "routify-app", 3 | "version": "1.0.0", 4 | "@comments scripts": { 5 | "dev": "run all dev:* scripts", 6 | "dev:routify": "generate Routify's routes.js on filechange", 7 | "build": "run all build:* scripts", 8 | "build:routify": "build routify", 9 | "serve": "serve content in 'dist' folder" 10 | }, 11 | "scripts": { 12 | "dev": "run-p dev:* serve:*", 13 | "dev:routify": "routify", 14 | "serve": "run-p serve:*", 15 | "serve:spa": "spassr", 16 | "serve:ssr": "spassr --ssr --port 5005", 17 | "build": "cross-env NODE_ENV=production run-s build:*", 18 | "build:routify": "routify -b", 19 | "test": "npm run test:dev && npm run test:build", 20 | "test:dev": "node test/lib/wrapper dev ava test/{common,dev}/**/*.test.js", 21 | "test:build": "node test/lib/wrapper build ava test/{common,build}/**/*.test.js" 22 | }, 23 | "devDependencies": { 24 | "@roxi/routify": "^2.14.0", 25 | "fkill": "^7.1.0", 26 | "npm-run-all": "^4.1.5", 27 | "svelte": "^3.35.0", 28 | "cross-env": "^7.0.3", 29 | "spassr": "^2.4.1-0" 30 | }, 31 | "routify": { 32 | "extensions": "svelte,html,svx,md" 33 | }, 34 | "@comment options": [ 35 | "config provides defaults for spassr, spank, tossr and poindexter.", 36 | "You can delete the field and this comment, if you use neither." 37 | ], 38 | "appConfig": { 39 | "port": 5000, 40 | "assetsDir": "static", 41 | "template": "static/__app.html", 42 | "distDir": "dist", 43 | "buildDir": "dist/build", 44 | "script": "dist/build/main.js" 45 | }, 46 | "spassr": { 47 | "ssrOptions": { 48 | "inlineDynamicImports": true 49 | } 50 | }, 51 | "ava": { 52 | "verbose": true 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /fragments/base/template/.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules/ 2 | .DS_Store 3 | **/.history 4 | src/tmp/ 5 | .routify 6 | .netlify 7 | assets/build 8 | .vercel -------------------------------------------------------------------------------- /fragments/base/template/src/App.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | __HTML__ 11 | 12 | 15 | -------------------------------------------------------------------------------- /fragments/base/template/src/App.svelte.fragment.js: -------------------------------------------------------------------------------- 1 | exports.patch = ({ placeholders, configs, stringify }) => { 2 | const config = configs.routifyRuntime 3 | if (config) { 4 | placeholders.script.push(`const config = ${stringify(config)}`) 5 | placeholders.html.push(``) 6 | } else placeholders.html.push(``) 7 | } 8 | -------------------------------------------------------------------------------- /fragments/base/template/src/main.js: -------------------------------------------------------------------------------- 1 | import HMR from '@roxi/routify/hmr' 2 | import App from './App.svelte' 3 | 4 | const app = HMR(App, { target: document.body }, 'routify-app') 5 | 6 | export default app 7 | -------------------------------------------------------------------------------- /fragments/base/template/src/pages/_fallback.svelte: -------------------------------------------------------------------------------- 1 |

404

2 | -------------------------------------------------------------------------------- /fragments/base/template/src/pages/_layout.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
__NAVIGATION__ __HEADER__
6 | 7 |
8 | __HTML__ 9 | 10 |
11 | 12 |
__FOOTER__
13 | -------------------------------------------------------------------------------- /fragments/base/template/src/pages/guide/_layout.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
13 |

Guide

14 | 15 | 21 |
22 | 23 |
24 |
25 | 26 | 27 | 56 | -------------------------------------------------------------------------------- /fragments/base/template/src/pages/guide/index.svelte: -------------------------------------------------------------------------------- 1 |

What's a fragment?

2 | 3 |

4 | A fragment is a template-component. By combining fragments, you can compose 5 | your own starter template. 6 |

7 | 8 |

9 | To see what's included in this template, have a look at the fragments to the 10 | left. 11 |

12 | 13 | You can remove this guide by deleting the src/pages/guide folder. 15 | -------------------------------------------------------------------------------- /fragments/base/template/src/pages/guide/routify.md: -------------------------------------------------------------------------------- 1 | ### Routify 2 | 3 | Routify is the router and along with Svelte it powers these templates. 4 | 5 | Routify outputs to `routify` by default. 6 | 7 | Configuration can be done in `package.json` and other places. For more info on configuration, see 8 | Routify - configuration 9 | 10 | For general usage, see Getting started. 11 | -------------------------------------------------------------------------------- /fragments/base/template/src/pages/index.svelte: -------------------------------------------------------------------------------- 1 |

App?!

2 |

A short introduction would have been nice here...

3 | 4 | guide 5 | -------------------------------------------------------------------------------- /fragments/base/template/static/__app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Svelte app 8 | 9 | 10 | 11 | 12 | 13 | 14 | __HEAD__ 15 | 16 | 17 | 18 | 19 | __BODY__ 20 | 21 | 22 | -------------------------------------------------------------------------------- /fragments/base/template/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roxiness/stackmix/7f0f0f2c4d2b57fa1ee37536c0f07f4e2b9d3beb/fragments/base/template/static/favicon.ico -------------------------------------------------------------------------------- /fragments/base/template/test/build/README.md: -------------------------------------------------------------------------------- 1 | # dir for build tests 2 | -------------------------------------------------------------------------------- /fragments/base/template/test/common/README.md: -------------------------------------------------------------------------------- 1 | # dir for build and dev tests 2 | -------------------------------------------------------------------------------- /fragments/base/template/test/common/base.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * These tests require ava and playwright 3 | * To install: 4 | * npm install --save-dev ava playwright 5 | */ 6 | 7 | const test = require('ava') 8 | const { pageMacro, baseUrl } = require('../lib') 9 | 10 | test('can see frontpage', pageMacro, async (t, page) => { 11 | await page.goto(baseUrl) 12 | t.assert(await page.waitForSelector('main')) 13 | }) 14 | -------------------------------------------------------------------------------- /fragments/base/template/test/dev/README.md: -------------------------------------------------------------------------------- 1 | # dir for dev tests 2 | -------------------------------------------------------------------------------- /fragments/base/template/test/lib/index.js: -------------------------------------------------------------------------------- 1 | // create Page macro 2 | const pageMacro = (() => { 3 | let browserPromise 4 | 5 | const pageMacro = async (t, callback) => { 6 | const { chromium } = require('playwright') 7 | 8 | if (!browserPromise) browserPromise = chromium.launch() 9 | 10 | const browser = await browserPromise 11 | const page = await browser.newPage() 12 | page.setDefaultTimeout(1000) 13 | try { 14 | await callback(t, page) 15 | } finally { 16 | await page.close() 17 | } 18 | } 19 | 20 | return pageMacro 21 | })() 22 | 23 | /** 24 | * wait 25 | * @param {string|number} time 26 | * @returns {Promise} 27 | */ 28 | const wait = time => new Promise(resolve => setTimeout(resolve, time)) 29 | 30 | /** 31 | * 32 | * @param {string|number} port 33 | * @param {string|number} timeout 34 | * @returns {boolean} 35 | */ 36 | async function checkPort(port, timeout) { 37 | const http = require('http') 38 | const timestamp = Date.now() 39 | let portFound = false 40 | while (!portFound && timestamp + timeout > Date.now()) { 41 | const req = http.request({ port }) 42 | req.on('response', () => (portFound = true)) 43 | req.on('error', err => {}) 44 | req.end() 45 | await wait(500) 46 | } 47 | return portFound 48 | } 49 | 50 | /** 51 | * creates baseUrl 52 | * @returns {string} 53 | */ 54 | function getBaseUrl() { 55 | const { port } = require('../../package.json').appConfig 56 | return `http://127.0.0.1:${port}/` 57 | } 58 | 59 | /** 60 | * returns absolute path from project-relative path 61 | * @param {...string} path 62 | * @returns {string} 63 | */ 64 | function resolve(...path) { 65 | return require('path').resolve(__dirname, '..', '..', ...path) 66 | } 67 | 68 | function getPkgJson() { 69 | return require(resolve('package.json')) 70 | } 71 | 72 | module.exports = { 73 | baseUrl: getBaseUrl(), 74 | checkPort, 75 | wait, 76 | pageMacro, 77 | resolve, 78 | get pkgJson() { 79 | return getPkgJson() 80 | }, 81 | get appConfig() { 82 | return getPkgJson().appConfig 83 | }, 84 | } 85 | -------------------------------------------------------------------------------- /fragments/base/template/test/lib/wrapper.js: -------------------------------------------------------------------------------- 1 | const { spawnSync, spawn } = require('child_process') 2 | const { checkPort, wait } = require('./') 3 | const fkill = require('fkill') 4 | const { port } = require('../../package.json').appConfig 5 | const timeout = 15000 // 15s 6 | 7 | const npm = /^win/.test(process.platform) ? 'npm.cmd' : 'npm' 8 | const npx = /^win/.test(process.platform) ? 'npx.cmd' : 'npx' 9 | 10 | // creates function with teardown script 11 | const createTeardown = pid => () => fkill(pid, { tree: true, force: true }) 12 | 13 | const setups = { 14 | async dev() { 15 | // run dev server 16 | const child = await spawn(npm, ['run', 'dev']) 17 | 18 | if (await checkPort(port, timeout)) { 19 | await wait(500) 20 | return createTeardown(child.pid) 21 | } else { 22 | createTeardown(child.pid)() 23 | throw Error(`dev on port ${port} timed out after ${timeout} ms`) 24 | } 25 | }, 26 | async build() { 27 | // build app 28 | spawnSync(npm, ['run', 'build']) 29 | 30 | // serve app 31 | const child = await spawn(npm, ['run', 'serve']) 32 | 33 | if (await checkPort(port, timeout)) { 34 | return createTeardown(child.pid) 35 | } else { 36 | createTeardown(child.pid)() 37 | throw Error(`build on port ${port} timed out after ${timeout} ms`) 38 | } 39 | }, 40 | } 41 | 42 | // wrap the CLI command 43 | runCliCommand() 44 | 45 | async function runCliCommand() { 46 | // assign `dev` or `build` to mode and `ava` to cmd 47 | const [mode, cmd, ...args] = process.argv.slice(2) 48 | 49 | console.log(`[app] setup ${mode} test...`) 50 | const teardown = await setups[mode]() 51 | console.log(`[app] setup ${mode} test... Done.`) 52 | console.log(`[app] run: ${cmd} ${args.join(' ')}`) 53 | const { status } = spawnSync(npx, [cmd, ...args], { stdio: 'inherit' }) 54 | console.log(`[app] teardown ${mode} test...`) 55 | teardown() 56 | console.log(`[app] teardown ${mode} test... Done.`) 57 | process.exit(status) 58 | } 59 | -------------------------------------------------------------------------------- /fragments/content/blueprint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'content', 3 | } 4 | -------------------------------------------------------------------------------- /fragments/content/template/src/pages/introduction/_layout.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /fragments/content/template/src/pages/introduction/index.svelte: -------------------------------------------------------------------------------- 1 |
2 |

Introduction

3 | 4 |

Why hello

5 |
6 | -------------------------------------------------------------------------------- /fragments/content/template/src/pages/posts/[slug].svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | {#if Post} 13 | {#await Post.component then page} 14 | 15 | {/await} 16 | {/if} 17 |
18 | -------------------------------------------------------------------------------- /fragments/content/template/src/pages/posts/_layout.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /fragments/content/template/src/pages/posts/entries/_layout.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /fragments/content/template/src/pages/posts/entries/html-ftw.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

HTML FTW

6 |

7 | There are two axes differentiating various variations of HTML as currently 8 | specified: SGML-based HTML versus XML-based HTML (referred to as XHTML) on 9 | one axis, and strict versus transitional (loose) versus frameset on the 10 | other axis. 11 |

12 | 13 |

14 | To understand the subtle differences between HTML and XHTML, consider the 15 | transformation of a valid and well-formed XHTML 1.0 document that adheres to 16 | Appendix C (see below) into a valid HTML 4.01 document. To make this 17 | translation requires the following steps: 18 |

19 | 20 |

21 | Those are the main changes necessary to translate a document from XHTML 1.0 22 | to HTML 4.01. To translate from HTML to XHTML would also require the 23 | addition of any omitted opening or closing tags. Whether coding in HTML or 24 | XHTML it may just be best to always include the optional tags within an HTML 25 | document rather than remembering which tags can be omitted. 26 |

27 | -------------------------------------------------------------------------------- /fragments/content/template/src/pages/posts/entries/secretpost.svelte: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fragments/content/template/src/pages/posts/entries/something-about-html.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Something about HTML

6 |
  • 7 | Most elements take the language-related attribute dir to specify text 8 | direction, such as with "rtl" for right-to-left text in, for example, 9 | Arabic, Persian or Hebrew. 10 |
  • 11 | 12 |
  • 13 | As of version 4.0, HTML defines a set of 252 character entity references and 14 | a set of 1,114,050 numeric character references, both of which allow 15 | individual characters to be written via simple markup, rather than 16 | literally. A literal character and its markup counterpart are considered 17 | equivalent and are rendered identically. 18 |
  • 19 | 20 |
  • 21 | HTML defines several data types for element content, such as script data and 22 | stylesheet data, and a plethora of types for attribute values, including 23 | IDs, names, URIs, numbers, units of length, languages, media descriptors, 24 | colors, character encodings, dates and times, and so on. All of these data 25 | types are specializations of character data. 26 |
  • 27 | -------------------------------------------------------------------------------- /fragments/content/template/src/pages/posts/index.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 |
    10 |

    Posts

    11 |
    12 | {#each posts as { meta, title, path }} 13 |
    14 |

    15 | {title} 17 |

    18 |

    19 | {meta.summary || meta.frontmatter.summary} 20 | Read more. 22 |

    23 | {meta.author || meta.frontmatter.author} 24 |
    25 | {/each} 26 |
    27 |
    28 | 29 | 48 | -------------------------------------------------------------------------------- /fragments/content/template/src/pages/posts/inlined.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | {#each entries as post} 12 |
    13 | {#await post.component then Post} 14 | 15 | {/await} 16 |
    17 | {/each} 18 | 19 | 28 | -------------------------------------------------------------------------------- /fragments/content/template/test/common/content.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * These tests require ava and playwright 3 | * To install: 4 | * npm install --save-dev ava playwright 5 | */ 6 | 7 | const test = require('ava') 8 | const { pageMacro } = require('../lib') 9 | 10 | test('can see frontpage', pageMacro, async (t, page) => { 11 | await page.goto('http://localhost:5000/posts') 12 | t.assert(await page.waitForSelector('"something about html"')) 13 | }) 14 | -------------------------------------------------------------------------------- /fragments/dev/blueprint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | configs: () => ({ 3 | packagejson: { workspaces: ['../../*'] }, 4 | }), 5 | } 6 | -------------------------------------------------------------------------------- /fragments/docker-nginx/blueprint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'feature', 3 | configs: () => ({ 4 | packagejson: { 5 | scripts: { 6 | 'buildimage:docker': 7 | 'run-s build:* && docker build -t routify-app:latest .', 8 | 'run:docker': 9 | 'docker run --name routify-frontend -d -p 8080:80 routify-app:latest', 10 | }, 11 | }, 12 | }), 13 | } 14 | -------------------------------------------------------------------------------- /fragments/docker-nginx/template/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx 2 | COPY dist /usr/share/nginx/html -------------------------------------------------------------------------------- /fragments/docker-ssr/blueprint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'feature', 3 | configs: () => ({ 4 | packagejson: { 5 | '@comments scripts': { 6 | 'build:docker': [ 7 | 'Builds a docker image. To disable this from the', 8 | "build step, you can rename it to 'build-docker'", 9 | ], 10 | }, 11 | scripts: { 12 | 'build:docker': 'docker build -t routify-app:latest .', 13 | 'run:docker': 14 | 'docker run --name routify -d -p 80:5005 routify-app:latest', 15 | }, 16 | }, 17 | }), 18 | } 19 | -------------------------------------------------------------------------------- /fragments/docker-ssr/template/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:current-alpine 2 | RUN apk add --no-cache git 3 | 4 | RUN mkdir /code 5 | WORKDIR /code 6 | 7 | COPY . /code/ 8 | RUN npm install 9 | 10 | CMD ["npm", "run", "serve:ssr"] 11 | 12 | 13 | -------------------------------------------------------------------------------- /fragments/docker-ssr/template/test/build/docker-ssr.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * These tests require ava and playwright 3 | * To install: 4 | * npm install --save-dev ava playwright 5 | */ 6 | 7 | const test = require('ava') 8 | const { pageMacro } = require('../pageMacro') 9 | const { checkPort } = require('../utils') 10 | const { spawnSync } = require('child_process') 11 | 12 | test.before(async t => { 13 | const npm = /^win/.test(process.platform) ? 'npm.cmd' : 'npm' 14 | spawnSync(npm, ['run', 'run:docker'], { stdio: 'inherit' }) 15 | await checkPort(80, 15000) 16 | }) 17 | 18 | test('can create an image', pageMacro, async (t, page) => { 19 | await page.goto('http://localhost:80') 20 | t.assert(await page.waitForSelector('main')) 21 | }) 22 | 23 | test.after(async t => { 24 | spawnSync('docker', ['rm', '--force', 'routify']) 25 | }) 26 | -------------------------------------------------------------------------------- /fragments/i18n/blueprint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'feature', 3 | configs: ({ getConfigString, stringify }) => ({ 4 | routifyRuntime: { 5 | urlTransform: 'urlTransform', 6 | }, 7 | }), 8 | } 9 | -------------------------------------------------------------------------------- /fragments/i18n/template/src/App.svelte.fragment.js: -------------------------------------------------------------------------------- 1 | exports.patch = ({ placeholders, configs, stringify }) => { 2 | placeholders.script.push( 3 | `import { urlTransform } from './components/Lang.svelte'`, 4 | ) 5 | } 6 | -------------------------------------------------------------------------------- /fragments/i18n/template/src/components/Lang.svelte: -------------------------------------------------------------------------------- 1 | 41 | 42 | 48 | 49 | 54 | -------------------------------------------------------------------------------- /fragments/i18n/template/src/pages/_layout.svelte.fragment.js: -------------------------------------------------------------------------------- 1 | module.exports.patch = ({ placeholders, configs }) => { 2 | placeholders.script.push(`import Lang from '../components/Lang.svelte'`) 3 | placeholders.navigation.push(`
    `) 4 | } 5 | -------------------------------------------------------------------------------- /fragments/i18n/template/src/pages/guide/i18n.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | {#if lang === 'en'} 7 |

    Internationalization

    8 |

    9 | In this example we're using urlTransform to store and fetch 10 | the current language ID in/from the url. We could also do this with a 11 | dynamic folder in the root, eg. src/pages/[language]. 12 |

    13 |

    14 | How localized data should be stored and resolved is entirely up to you. 15 | For simplicity, we've kept it all in one file. 16 |

    17 |

    18 | Click the language dropdown to change the language of this page. 19 |

    20 | {:else if lang === 'de'} 21 |

    Internationalisierung

    22 |

    23 | In diesem Beispiel verwenden wir urlTransform, um die 24 | aktuelle Sprach-ID in / aus der URL zu speichern und abzurufen. Wir 25 | könnten dies auch mit einem dynamischen Ordner im Stammverzeichnis tun, 26 | z. 27 | src/pages/[language]. 28 |

    29 |

    30 | Wie lokalisierte Daten gespeichert und aufgelöst werden sollen, liegt 31 | ganz bei Ihnen. Der Einfachheit halber haben wir alles in einer Datei 32 | gespeichert. 33 |

    34 |

    35 | Klicken Sie auf die Sprach-Dropdown-Liste, um die Sprache dieser Seite 36 | zu ändern. 37 |

    38 | {:else if lang === 'fr'} 39 |

    Internationalisation

    40 |

    41 | Dans cet exemple, nous utilisons urlTransform pour stocker 42 | et récupérer l'ID de langue actuel dans / depuis l'url. Nous pourrions 43 | également le faire avec un dossier dynamique à la racine, par exemple. 44 | src/pages/[language]. 45 |

    46 |

    47 | La manière dont les données localisées doivent être stockées et résolues 48 | dépend entièrement de vous. Pour plus de simplicité, nous avons tout 49 | gardé en un déposer. 50 |

    51 |

    52 | Cliquez sur la liste déroulante des langues pour changer la langue de 53 | cette page. 54 |

    55 | {/if} 56 | 57 | 63 | -------------------------------------------------------------------------------- /fragments/i18n/template/test/common/i18n.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * These tests require ava and playwright 3 | * To install: 4 | * npm install --save-dev ava playwright 5 | */ 6 | 7 | const test = require('ava') 8 | const { pageMacro } = require('../lib') 9 | 10 | test('default page shows English content', pageMacro, async (t, page) => { 11 | await page.goto('http://localhost:5000/guide/i18n') 12 | t.assert(await page.waitForSelector('"Internationalization"')) 13 | }) 14 | 15 | test('German page shows German content', pageMacro, async (t, page) => { 16 | await page.goto('http://localhost:5000/de/guide/i18n') 17 | t.assert(await page.waitForSelector('"Internationalisierung"')) 18 | }) 19 | 20 | test('French page shows French content', pageMacro, async (t, page) => { 21 | await page.goto('http://localhost:5000/fr/guide/i18n') 22 | t.assert(await page.waitForSelector('"Internationalisation"')) 23 | }) 24 | -------------------------------------------------------------------------------- /fragments/markdown/blueprint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | default: true, 3 | type: 'feature', 4 | imports: { 5 | mdsvex: ['mdsvex', 'mdsvex'], 6 | slug: ['remark-slug'], 7 | }, 8 | configs: ({ getConfigString, $require }) => ({ 9 | options: { base: { useMarkdown: true } }, 10 | packagejson: require('./package.json'), 11 | svelte: { 12 | extensions: ["'.md'", "'.svelte'"], 13 | preprocess: [$require('mdsvex')(getConfigString('mdsvex'))], 14 | }, 15 | mdsvex: { 16 | remarkPlugins: [$require('slug')], 17 | layout: { 18 | blog: "'src/components/Card.svelte'", 19 | }, 20 | extension: "'md'", 21 | }, 22 | }), 23 | } 24 | -------------------------------------------------------------------------------- /fragments/markdown/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "mdsvex": "^0.8.9", 4 | "remark-slug": "^6.0.0", 5 | "routify-plugin-frontmatter": "^1.0.1" 6 | }, 7 | "routify": { 8 | "extensions": [ 9 | "md", 10 | "svelte" 11 | ], 12 | "plugins": { 13 | "routify-plugin-frontmatter": {} 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /fragments/markdown/template/src/components/Card.svelte: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fragments/markdown/template/src/pages/guide/markdown.md: -------------------------------------------------------------------------------- 1 | ### Markdown 2 | 3 | Markdown includes `mdsvex`, `remark-slug` and `routify-plugin-frontmatter` 4 | -------------------------------------------------------------------------------- /fragments/markdown/template/test/common/mdsvex.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * These tests require ava and playwright 3 | * To install: 4 | * npm install --save-dev ava playwright 5 | */ 6 | 7 | const test = require('ava') 8 | const { pageMacro } = require('../lib') 9 | 10 | test('can view a markdown page', pageMacro, async (t, page) => { 11 | await page.goto('http://localhost:5000/guide/markdown') 12 | // await new Promise(resolve => setTimeout(resolve, 100)) 13 | t.assert(await page.waitForSelector('"Markdown"')) 14 | }) 15 | -------------------------------------------------------------------------------- /fragments/milligram/blueprint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'feature', 3 | default: true, 4 | } 5 | -------------------------------------------------------------------------------- /fragments/milligram/template/src/pages/guide/milligram.svelte: -------------------------------------------------------------------------------- 1 |

    Milligram.css

    2 | 3 |

    Milligram is a lightweight css framework.

    4 |

    5 | It's the default CSS framework for our examples and templates since it 6 | doesn't pollute the HTML with proprietary classes or contrived structures. 7 |

    8 |

    To remove it, simply delete it from __app.html

    9 |

    For more info: milligram

    10 | -------------------------------------------------------------------------------- /fragments/milligram/template/static/__app.html.fragment.js: -------------------------------------------------------------------------------- 1 | module.exports.patch = ({ placeholders }) => { 2 | placeholders.head.push(` 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | `) 17 | } 18 | -------------------------------------------------------------------------------- /fragments/mobile/blueprint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'template', 3 | dependencies: ['milligram', 'content'], 4 | } 5 | -------------------------------------------------------------------------------- /fragments/mobile/template/src/components/Decorator.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 |
    9 | 10 |
    11 |
    12 | -------------------------------------------------------------------------------- /fragments/mobile/template/src/components/navigation/Navbar.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /fragments/mobile/template/src/components/navigation/Overlay.svelte: -------------------------------------------------------------------------------- 1 | 37 | 38 | 39 |
    40 | 41 |
    42 | 43 | 44 |
    45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /fragments/mobile/template/src/pages/_layout.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
    9 |
    __NAVIGATION__ __HEADER__
    10 |
    11 | __HTML__ 12 | 15 |
    16 | 17 |
    18 | 19 | __FOOTER__ 20 |
    21 |
    22 | 23 | 26 | -------------------------------------------------------------------------------- /fragments/mobile/template/src/pages/guide/mobile.svelte: -------------------------------------------------------------------------------- 1 |

    Mobile template

    2 | -------------------------------------------------------------------------------- /fragments/mobile/template/src/pages/home/_layout.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /fragments/mobile/template/src/pages/home/index.svelte: -------------------------------------------------------------------------------- 1 |
    2 |

    home

    3 |
    4 | -------------------------------------------------------------------------------- /fragments/mobile/template/src/pages/index.svelte: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fragments/mobile/template/static/__app.html.fragment.js: -------------------------------------------------------------------------------- 1 | module.exports.patch = ({ placeholders }) => { 2 | placeholders.head.push('') 3 | } 4 | -------------------------------------------------------------------------------- /fragments/mobile/template/static/mobile.css: -------------------------------------------------------------------------------- 1 | #app { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100vh; 5 | } 6 | main { 7 | flex-grow: 1; 8 | overflow: hidden; 9 | } 10 | .node_posts__layout { 11 | background: #0bf5cc; 12 | } 13 | .node_guide__layout { 14 | background: #a1fac3; 15 | } 16 | .node_home__layout { 17 | background: #88f0d0; 18 | } 19 | .node_introduction__layout { 20 | background: #7fc5bb; 21 | } 22 | .posts .post { 23 | background: white; 24 | } 25 | .transition { 26 | overflow: auto; 27 | } 28 | .container { 29 | padding-top: 32px; 30 | padding-bottom: 32px; 31 | } 32 | 33 | /* .transition[style^=' animation'] { 34 | overflow: hidden; 35 | } */ 36 | 37 | footer nav { 38 | text-transform: uppercase; 39 | background: white; 40 | display: flex; 41 | box-shadow: 0 6px 20px 0 rgba(0, 0, 0, 0.19); 42 | justify-content: space-evenly; 43 | } 44 | 45 | footer a { 46 | font-size: 14px; 47 | color: #aaa; 48 | text-transform: uppercase; 49 | flex-grow: 1; 50 | text-align: center; 51 | line-height: 64px; 52 | } 53 | 54 | footer .overlay { 55 | transition: all 0.3s; 56 | position: fixed; 57 | border-bottom: 3px solid #a1fac3; 58 | pointer-events: none; 59 | } 60 | -------------------------------------------------------------------------------- /fragments/navigation/blueprint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | default: true, 3 | type: 'template', 4 | } 5 | -------------------------------------------------------------------------------- /fragments/navigation/template/src/components/Navbar/Bar.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | -------------------------------------------------------------------------------- /fragments/navigation/template/src/components/Navbar/Navigation.svelte: -------------------------------------------------------------------------------- 1 | 12 | 23 | 24 |
      25 | {#each items as { path, title, children, ...rest }} 26 |
    • 27 | 28 | {title} 29 | 30 | 31 | {#if items && _depth < maxDepth && shouldExplode(path)} 32 | 33 | {/if} 34 |
    • 35 | {/each} 36 |
    37 | 38 | 55 | -------------------------------------------------------------------------------- /fragments/navigation/template/src/pages/_layout.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
    7 | 12 |
    13 |
    __HEADER__
    14 |
    15 | __HTML__ 16 | 17 |
    18 |
    __FOOTER__
    19 |
    20 |
    21 | 22 | 39 | -------------------------------------------------------------------------------- /fragments/navigation/template/test/common/navigation.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * These tests require ava and playwright 3 | * To install: 4 | * npm install --save-dev ava playwright 5 | */ 6 | 7 | const test = require('ava') 8 | const { pageMacro } = require('../pageMacro') 9 | 10 | test('can navigate', pageMacro, async (t, page) => { 11 | await page.goto('http://localhost:5000/') 12 | t.assert(await page.waitForSelector('nav')) 13 | 14 | await page.click('"posts"') 15 | t.assert(await page.waitForSelector('"something about html"')) 16 | }) 17 | -------------------------------------------------------------------------------- /fragments/nollup/blueprint.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** @type {import('canvasit')['Blueprint']} */ 4 | module.exports = { 5 | name: 'rollup + nollup', 6 | type: 'bundler', 7 | dependencies: ['rollup'], 8 | imports: { 9 | assetsDir: ['./package.json', 'appConfig', 'assetsDir'], 10 | buildDir: ['./package.json', 'appConfig', 'buildDir'], 11 | distDir: ['./package.json', 'appConfig', 'distDir'], 12 | port: ['./package.json', 'appConfig', 'port'], 13 | template: ['./package.json', 'appConfig', 'template'], 14 | relative: ['path', 'relative'], 15 | svelte: ['rollup-plugin-svelte-hot'], 16 | Hmr: ['rollup-plugin-hot'], 17 | }, 18 | configs: ({ $require }) => { 19 | return { 20 | nollup: { 21 | hot: 'true', 22 | contentBase: $require('assetsDir') + ', // static', 23 | publicPath: 24 | $require('relative')( 25 | $require('distDir') + ', ' + $require('buildDir'), 26 | ) + ', // build', 27 | historyApiFallback: 28 | $require('relative')( 29 | $require('assetsDir') + ', ' + $require('template'), 30 | ) + ', // __app.html', 31 | port: $require('port'), 32 | }, 33 | rollup: { 34 | plugins: [ 35 | `!production && isNollup && ${$require( 36 | 'Hmr', 37 | )}({ inMemory: true, public: ${$require( 38 | 'assetsDir', 39 | )}, }), //, refresh only updated code`, 40 | '!production && !isNollup && livereload(distDir), // refresh entire window when code is updated', 41 | ], 42 | watch: { 43 | clearScreen: 'false', 44 | buildDelay: '100', 45 | }, 46 | }, 47 | packagejson: require('./template/package.json'), 48 | } 49 | }, 50 | hooks: { 51 | afterPatch: ({ transform }) => { 52 | // remove unconditional livereload. We only want it when nollup isn't running 53 | transform('rollup.config.js', str => 54 | str.replace(/.*'!production && livereload\\(distDir\\)'.*/, ''), 55 | ) 56 | }, 57 | }, 58 | } 59 | -------------------------------------------------------------------------------- /fragments/nollup/template/.nolluprc.js: -------------------------------------------------------------------------------- 1 | __IMPORTS__ 2 | 3 | __CONSTANTS__ 4 | 5 | module.exports = __CONFIG__ 6 | -------------------------------------------------------------------------------- /fragments/nollup/template/.nolluprc.js.fragment.js: -------------------------------------------------------------------------------- 1 | exports.patch = ({ placeholders, configs, stringify }) => { 2 | placeholders.config.push(stringify(configs.nollup)) 3 | } 4 | -------------------------------------------------------------------------------- /fragments/nollup/template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "@comments scripts": { 3 | "dev:rollup": "develop with features like SSR and serviceworker enabled", 4 | "build:rollup": "build rollup" 5 | }, 6 | "scripts": { 7 | "dev": "run-p dev:routify dev:nollup", 8 | "dev:nollup": "nollup -c", 9 | "dev:ssr": "run-p dev:routify dev:rollup serve" 10 | }, 11 | "devDependencies": { 12 | "nollup": "^0.15.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /fragments/nollup/template/rollup.config.js.fragment.js: -------------------------------------------------------------------------------- 1 | exports.patch = ({ placeholders, configs, stringify }) => { 2 | placeholders.constants.push('const isNollup = !!process.env.NOLLUP') 3 | } 4 | -------------------------------------------------------------------------------- /fragments/postcss/blueprint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'feature', 3 | dependencies: ['autoPreprocess'], 4 | configs: ({ getConfig }) => ({ 5 | packagejson: require('./package.json'), 6 | autoPreprocess: { 7 | postcss: getConfig('postcss'), 8 | }, 9 | postcss: { 10 | plugins: [], 11 | }, 12 | }), 13 | } 14 | -------------------------------------------------------------------------------- /fragments/postcss/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "postcss": "^8.2.4" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /fragments/rollup/blueprint.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** @type {import('canvasit')['Blueprint']} */ 4 | module.exports = { 5 | type: 'bundler', 6 | imports: { 7 | svelte: ['rollup-plugin-svelte'], 8 | resolve: ['@rollup/plugin-node-resolve'], 9 | commonjs: ['@rollup/plugin-commonjs'], 10 | livereload: ['rollup-plugin-livereload'], 11 | terser: ['rollup-plugin-terser', 'terser'], 12 | buildDir: ['./package.json', 'appConfig', 'buildDir'], 13 | distDir: ['./package.json', 'appConfig', 'distDir'], 14 | }, 15 | configs: ({ getConfigString, $require }) => ({ 16 | test: { 17 | domain: "'localhost'", 18 | port: '5000', 19 | }, 20 | rollupResolve: { 21 | browser: 'true', 22 | dedupe: 'importee => !!importee.match(/svelte(\\/|$)/)', 23 | }, 24 | rollup: { 25 | preserveEntrySignatures: 'false', 26 | input: ['`src/main.js`'], 27 | output: { 28 | sourcemap: 'true', 29 | format: "'esm'", 30 | dir: $require('buildDir'), 31 | // for performance, disabling filename hashing in development 32 | chunkFileNames: "`[name]${production && '-[hash]' || ''}.js`", 33 | }, 34 | plugins: [ 35 | $require('svelte')(getConfigString('svelte')), 36 | 37 | // resolve matching modules from current working directory 38 | $require('resolve')(getConfigString('rollupResolve')), 39 | $require('commonjs')(), 40 | 41 | `production && ${$require('terser')()}`, 42 | `!production && ${$require('livereload')( 43 | $require('distDir'), 44 | )} , // refresh entire window when code is updated`, 45 | ], 46 | watch: { clearScreen: 'false' }, 47 | }, 48 | packagejson: require('./template/package.json'), 49 | }), 50 | } 51 | -------------------------------------------------------------------------------- /fragments/rollup/template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "@comments scripts": { 3 | "dev:rollup": "develop with features like SSR and serviceworker enabled", 4 | "build:rollup": "build rollup" 5 | }, 6 | "scripts": { 7 | "dev:rollup": "rollup -cw", 8 | "build:rollup": "rollup -c" 9 | }, 10 | "devDependencies": { 11 | "@rollup/plugin-node-resolve": "^11.2.0", 12 | "@rollup/plugin-commonjs": "^17.1.0", 13 | "rollup": "^2.41.0", 14 | "rollup-plugin-livereload": "^2.0.0", 15 | "rollup-plugin-svelte": "^6.1.0", 16 | "rollup-plugin-hot": "^0.1.1", 17 | "rollup-plugin-svelte-hot": "^0.13.0", 18 | "rollup-plugin-terser": "^7.0.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /fragments/rollup/template/rollup.config.js: -------------------------------------------------------------------------------- 1 | import { removeSync } from 'fs-extra' 2 | __IMPORTS__ 3 | 4 | __CONSTANTS__ 5 | 6 | __LOGIC__ 7 | 8 | __FUNCTIONS__ 9 | 10 | export default __CONFIG__ 11 | 12 | __ESM__ 13 | -------------------------------------------------------------------------------- /fragments/rollup/template/rollup.config.js.fragment.js: -------------------------------------------------------------------------------- 1 | exports.patch = ({ placeholders, configs, stringify, $require }) => { 2 | placeholders.config.push(stringify(configs.rollup)) 3 | placeholders.logic.push(`// clear previous builds`) 4 | placeholders.logic.push(`removeSync(${$require('distDir')})`) 5 | placeholders.constants.push( 6 | `const production = process.env['NODE_ENV'] === 'production'`, 7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /fragments/rollup/template/src/pages/guide/rollup.svelte: -------------------------------------------------------------------------------- 1 |

    Rollup

    2 | 3 |

    Rollup is the default Svelte/Routify bundler.

    4 |

    5 | It doesn't offer "no-bundling", but thanks to libraries like nollup it can offer the same lightning fast rebuilds. 7 |

    8 |

    9 | Rollup runs automatically with npm run dev and 10 | npm run build. 11 |

    12 |

    13 | To run Rollup in isolation, run npm run dev:rollup or 14 | npm run build:rollup 15 |

    16 | -------------------------------------------------------------------------------- /fragments/rollup/template/static/__app.html.fragment.js: -------------------------------------------------------------------------------- 1 | exports.patch = ({ placeholders, configs, stringify }) => { 2 | placeholders.head.push('') 3 | placeholders.head.push( 4 | '', 5 | ) 6 | } 7 | -------------------------------------------------------------------------------- /fragments/scroll-navigation/blueprint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'template', 3 | } 4 | -------------------------------------------------------------------------------- /fragments/scroll-navigation/template/src/pages/_layout.svelte: -------------------------------------------------------------------------------- 1 | 26 | 27 | __HTML__ 28 | 29 |
    30 | 31 |
    32 | 33 | 47 | 48 | 71 | -------------------------------------------------------------------------------- /fragments/scroll-navigation/template/src/pages/frontpage/_layout.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 |

    Go to the front page to see this page

    4 | 5 | 6 | -------------------------------------------------------------------------------- /fragments/scroll-navigation/template/src/pages/frontpage/contact.svelte: -------------------------------------------------------------------------------- 1 |

    Contact

    2 | 3 | 4 | -------------------------------------------------------------------------------- /fragments/scroll-navigation/template/src/pages/frontpage/features.svelte: -------------------------------------------------------------------------------- 1 | 2 |

    Features

    3 | -------------------------------------------------------------------------------- /fragments/scroll-navigation/template/src/pages/frontpage/home.svelte: -------------------------------------------------------------------------------- 1 |

    Home

    2 | 3 | 4 | -------------------------------------------------------------------------------- /fragments/scroll-navigation/template/src/pages/frontpage/story.svelte: -------------------------------------------------------------------------------- 1 |

    Story

    2 | 3 | 4 | -------------------------------------------------------------------------------- /fragments/scroll-navigation/template/src/pages/guide/single-page.svelte: -------------------------------------------------------------------------------- 1 |

    Single page navigation

    2 | 3 |

    Navigational scrolling - not to be confused with single page application.

    4 | 5 |

    6 | All pages in `/frontpage` are plain files, but we could also have used 7 | folders. 8 |

    9 | -------------------------------------------------------------------------------- /fragments/scroll-navigation/template/src/pages/index.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 |
    22 | 23 | {#each nodes as node, index} 24 | 27 | {#await node.componentWithIndex then [Cmp, IndexCmp]} 28 | 29 |
    30 |
    31 | 32 | 33 | 34 | 35 | 36 |
    37 |
    38 | {/await} 39 | {/each} 40 |
    41 | 42 | 52 | -------------------------------------------------------------------------------- /fragments/serviceworker/blueprint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'feature', 3 | configs: ({ getConfig, stringify }) => ({ 4 | packagejson: { 5 | devDependencies: { 6 | 'workbox-cli': '^6.1.0', 7 | esbuild: '^0.8.42', 8 | }, 9 | scripts: { 10 | 'build:pwa-bundle': 11 | 'esbuild --bundle src/sw.js --outfile=public/sw.generated.js --define:process.env.NODE_ENV="\'production\'"', 12 | 'build:pwa-inject': 'workbox injectManifest', 13 | }, 14 | }, 15 | }), 16 | hooks: { 17 | afterPatch: ctx => { 18 | ctx.writeTo( 19 | 'package.json', 20 | JSON.stringify(ctx.configs.packagejson, null, 2), 21 | ) 22 | }, 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /fragments/serviceworker/template/src/App.svelte.fragment.js: -------------------------------------------------------------------------------- 1 | module.exports.patch = ({ placeholders }) => { 2 | placeholders.imports.push( 3 | 'import Serviceworker from "./components/Serviceworker.svelte"', 4 | ) 5 | placeholders.html.push('') 6 | } 7 | -------------------------------------------------------------------------------- /fragments/serviceworker/template/src/components/Serviceworker.svelte: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /fragments/serviceworker/template/src/pages/guide/serviceworker.md: -------------------------------------------------------------------------------- 1 | ### Serviceworker 2 | 3 | The serviceworker prodivides offline availability and precaching 4 | -------------------------------------------------------------------------------- /fragments/serviceworker/template/src/sw.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import { 4 | registerRoute, 5 | setDefaultHandler, 6 | setCatchHandler, 7 | } from 'workbox-routing' 8 | import { 9 | CacheFirst, 10 | NetworkFirst, 11 | StaleWhileRevalidate, 12 | } from 'workbox-strategies' 13 | import { skipWaiting, clientsClaim } from 'workbox-core' 14 | import { precacheAndRoute, matchPrecache } from 'workbox-precaching' 15 | import { ExpirationPlugin } from 'workbox-expiration' 16 | import { RoutifyPlugin, freshCacheData } from '@roxi/routify/workbox-plugin' 17 | 18 | /********** 19 | * CONFIG * 20 | **********/ 21 | 22 | const entrypointUrl = '__app.html' // entrypoint 23 | const fallbackImage = '404.svg' 24 | const files = self.__WB_MANIFEST // files matching globDirectory and globPattern in rollup.config.js 25 | 26 | const externalAssetsConfig = () => ({ 27 | cacheName: 'external', 28 | plugins: [ 29 | RoutifyPlugin({ 30 | validFor: 60, // cache is considered fresh for n seconds. 31 | }), 32 | new ExpirationPlugin({ 33 | maxEntries: 50, // last used entries will be purged when we hit this limit 34 | purgeOnQuotaError: true, // purge external assets on quota error 35 | }), 36 | ], 37 | }) 38 | 39 | /************** 40 | * INITIALIZE * 41 | **************/ 42 | 43 | /** 44 | * precache all files 45 | * remember to precache __app.html and 404.svg if caching of all files is disabled 46 | */ 47 | precacheAndRoute(files) 48 | 49 | /** precache only fallback files */ 50 | // precacheAndRoute(files.filter(file => 51 | // ['__app.html', '404.svg'] 52 | // .includes(file.url) 53 | // )) 54 | 55 | skipWaiting() // auto update service workers across all tabs when new release is available 56 | clientsClaim() // take control of client without having to wait for refresh 57 | 58 | /** 59 | * manually upgrade service worker by sending a SKIP_WAITING message. 60 | * (remember to disable skipWaiting() above) 61 | */ 62 | // addEventListener('message', event => { if (event.data && event.data.type === 'SKIP_WAITING') skipWaiting(); }); 63 | 64 | /********** 65 | * ROUTES * 66 | **********/ 67 | 68 | // serve local pages from the SPA entry point (__app.html) 69 | registerRoute(isLocalPage, matchPrecache(entrypointUrl)) 70 | 71 | // serve local assets from cache first 72 | registerRoute(isLocalAsset, new CacheFirst()) 73 | 74 | // serve external assets from cache if they're fresh 75 | registerRoute(hasFreshCache, new CacheFirst(externalAssetsConfig())) 76 | 77 | // serve external pages and assets 78 | setDefaultHandler(new NetworkFirst(externalAssetsConfig())) 79 | 80 | // serve a fallback for 404s if possible or respond with an error 81 | setCatchHandler(async ({ event }) => { 82 | switch (event.request.destination) { 83 | case 'document': 84 | return await matchPrecache(entrypointUrl) 85 | case 'image': 86 | return await matchPrecache(fallbackImage) 87 | default: 88 | return Response.error() 89 | } 90 | }) 91 | 92 | /********** 93 | * CONDITIONS * 94 | **********/ 95 | 96 | function isLocalAsset({ url, request }) { 97 | return url.host === self.location.host && request.destination != 'document' 98 | } 99 | function isLocalPage({ url, request }) { 100 | return url.host === self.location.host && request.destination === 'document' 101 | } 102 | function hasFreshCache(event) { 103 | return !!freshCacheData(event) 104 | } 105 | 106 | /** Example condition */ 107 | function hasWitheringCache(event) { 108 | const cache = freshCacheData(event) 109 | if (cache) { 110 | const { cachedAt, validFor, validLeft, validUntil } = cache 111 | // return true if half the fresh time has passed 112 | return validFor / 2 > validFor - validLeft 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /fragments/serviceworker/template/static/images/touch-icons/logo-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roxiness/stackmix/7f0f0f2c4d2b57fa1ee37536c0f07f4e2b9d3beb/fragments/serviceworker/template/static/images/touch-icons/logo-192.png -------------------------------------------------------------------------------- /fragments/serviceworker/template/static/images/touch-icons/logo-800.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roxiness/stackmix/7f0f0f2c4d2b57fa1ee37536c0f07f4e2b9d3beb/fragments/serviceworker/template/static/images/touch-icons/logo-800.png -------------------------------------------------------------------------------- /fragments/serviceworker/template/static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "background_color": "#ffffff", 3 | "theme_color": "#E938C2", 4 | "name": "Routify app", 5 | "short_name": "Routify app", 6 | "start_url": "/", 7 | "display": "standalone", 8 | "icons": [ 9 | { 10 | "src": "/images/touch-icons/logo-192.png", 11 | "sizes": "192x192", 12 | "type": "image/png" 13 | }, 14 | { 15 | "src": "/images/touch-icons/logo-800.png", 16 | "sizes": "800x800", 17 | "type": "image/png", 18 | "purpose": "maskable any" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /fragments/serviceworker/template/workbox-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | globDirectory: 'public', 3 | globPatterns: ['**/*.{js,css,svg}', '__app.html'], 4 | swSrc: `public/sw.generated.js`, 5 | swDest: `public/sw.generated.js`, 6 | maximumFileSizeToCacheInBytes: 10000000, // 10 MB, 7 | } 8 | -------------------------------------------------------------------------------- /fragments/snowpack/blueprint.js: -------------------------------------------------------------------------------- 1 | /** @type {import('canvasit')['Blueprint']} */ 2 | module.exports = { 3 | type: 'bundler', 4 | configs: () => ({ 5 | packagejson: require('./package.json'), 6 | }), 7 | hooks: { 8 | afterPatch: ctx => { 9 | const parts = ctx.parseImports(ctx.stringify(ctx.configs.svelte)) 10 | ctx.writeTo( 11 | 'svelte.config.js', 12 | ` 13 | ${parts.imports.join('\n')} 14 | ${parts.declarations.join('\n')} 15 | const production = process.env.NODE_ENV === 'production' 16 | module.exports = ${parts.body} 17 | `, 18 | ) 19 | }, 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /fragments/snowpack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dev": "run-p dev:*", 4 | "dev:snowpack": "snowpack dev", 5 | "build:snowpack": "snowpack build" 6 | }, 7 | "devDependencies": { 8 | "@snowpack/plugin-dotenv": "^2.0.5", 9 | "@snowpack/plugin-svelte": "^3.5.2", 10 | "snowpack": "^3.0.13" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /fragments/snowpack/template/snowpack.config.js: -------------------------------------------------------------------------------- 1 | const { assetsDir, distDir } = require('./package.json').appConfig 2 | 3 | /** @type {import("snowpack").SnowpackUserConfig } */ 4 | module.exports = { 5 | mount: { 6 | [assetsDir]: { url: '/', static: true }, 7 | src: { url: '/build' }, 8 | '.routify': { url: '/.routify' }, 9 | }, 10 | routes: [{ match: 'routes', src: '.*', dest: '/__app.html' }], 11 | plugins: ['@snowpack/plugin-svelte', '@snowpack/plugin-dotenv'], 12 | packageOptions: { 13 | knownEntrypoints: [ 14 | /* ... */ 15 | ], 16 | /* ... */ 17 | }, 18 | devOptions: { 19 | open: 'none', 20 | port: 5000, 21 | /* ... */ 22 | }, 23 | buildOptions: { 24 | out: distDir, 25 | /* ... */ 26 | }, 27 | alias: { 28 | /* ... */ 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /fragments/snowpack/template/src/pages/guide/snowpack.md: -------------------------------------------------------------------------------- 1 | #### Snowpack 2 | 3 | Snowpack is a lightning-fast frontend build tool, designed for the modern web. It is an alternative to heavier, more complex bundlers like webpack or Parcel in your development workflow. Snowpack leverages JavaScript's native module system (known as ESM) to avoid unnecessary work and stay fast no matter how big your project grows. 4 | -------------------------------------------------------------------------------- /fragments/snowpack/template/static/__app.html.fragment.js: -------------------------------------------------------------------------------- 1 | exports.patch = ({ placeholders, configs, stringify }) => { 2 | placeholders.head.push('') 3 | placeholders.head.push( 4 | '', 5 | ) 6 | } 7 | -------------------------------------------------------------------------------- /fragments/spank/blueprint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'Spank (Static Site Generator)', 3 | type: 'feature', 4 | configs: ({ getConfig, stringify }) => ({ 5 | packagejson: require('./package.json'), 6 | }), 7 | } 8 | -------------------------------------------------------------------------------- /fragments/spank/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build:static": "spank" 4 | }, 5 | "devDependencies": { 6 | "spank": "^1.5.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /fragments/spank/template/src/pages/guide/spank.md: -------------------------------------------------------------------------------- 1 | ### Spank (Static Site Generation) 2 | 3 | `spank` crawls your site and produces a corresponding HTML file for each page it encounters. 4 | 5 | [Official documentation](https://github.com/roxiness/spank) 6 | -------------------------------------------------------------------------------- /fragments/spank/template/test/build/spank.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * These tests require ava and playwright 3 | * To install: 4 | * npm install --save-dev ava playwright 5 | */ 6 | 7 | const { existsSync, readFileSync } = require('fs') 8 | const test = require('ava') 9 | const { resolve, pkgJson } = require('../lib') 10 | const { distDir } = pkgJson.appConfig 11 | 12 | test('index.html was prerenderd', async (t, page) => { 13 | const indexHtml = resolve(distDir, 'index.html') 14 | t.assert(existsSync(indexHtml), 'index.html should exist') 15 | 16 | const content = readFileSync(indexHtml, 'utf-8') 17 | t.regex( 18 | content, 19 | /
    /, 20 | "index.html's body should have prerendered content", 21 | ) 22 | }) 23 | -------------------------------------------------------------------------------- /fragments/tailwindcss/blueprint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'feature', 3 | dependencies: ['postcss'], 4 | imports: { 5 | tailwind: ['tailwindcss'], 6 | postcssImport: ['postcss-import'], 7 | }, 8 | configs: ({ getConfigString, getConfig, $require }) => ({ 9 | packagejson: require('./package.json'), 10 | autoPreprocess: { 11 | postcss: getConfig('postcss'), 12 | }, 13 | 14 | tailwindcss: { 15 | mode: "'jit'", 16 | darkMode: "'class'", 17 | future: { 18 | removeDeprecatedGapUtilities: 'true', 19 | purgeLayersByDefault: 'true', 20 | }, 21 | plugins: [], 22 | purge: { 23 | content: ["'./src/**/*.svelte'"], 24 | enabled: 'production', 25 | }, 26 | }, 27 | postcss: { 28 | plugins: [ 29 | $require('tailwind')(getConfigString('tailwindcss')), 30 | $require('postcssImport'), 31 | ], 32 | }, 33 | }), 34 | hooks: { 35 | beforeConfig(ctx) { 36 | ctx.prompt 37 | }, 38 | }, 39 | } 40 | -------------------------------------------------------------------------------- /fragments/tailwindcss/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "autoprefixer": "^10.2.3", 4 | "cssnano": "^4.1.10", 5 | "postcss-import": "^13.0.0", 6 | "tailwindcss": "^2.1.1" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /fragments/tailwindcss/template/.browserslistrc: -------------------------------------------------------------------------------- 1 | last 8 version -------------------------------------------------------------------------------- /fragments/tailwindcss/template/src/App.svelte.fragment.js: -------------------------------------------------------------------------------- 1 | exports.patch = ({ placeholders, configs, stringify }) => { 2 | placeholders.style.push(` 3 | @tailwind base; 4 | @tailwind components; 5 | @tailwind utilities;`) 6 | } 7 | -------------------------------------------------------------------------------- /fragments/tailwindcss/template/src/pages/guide/tailwind.svelte: -------------------------------------------------------------------------------- 1 |

    Tailwindcss

    2 | 3 |
    4 |

    5 | It's running with tailwind! 6 |

    7 |
    8 | 9 |

    Instructions to follow...

    10 | -------------------------------------------------------------------------------- /fragments/vercel/blueprint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'feature', 3 | } 4 | -------------------------------------------------------------------------------- /fragments/vercel/template/api/vercel-ssr/build.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | const { existsSync } = require('fs') 3 | const { execSync } = require('child_process') 4 | const { rollup } = require('rollup') 5 | 6 | const shouldBuildSpa = 7 | process.env.NOW_GITHUB_DEPLOYMENT || process.env.NOW_BUILDER 8 | const script = resolve(__dirname, '../../dist/build/main.js') 9 | const bundlePath = resolve(__dirname, '../../dist/build/bundle.js') 10 | 11 | build() 12 | 13 | async function build() { 14 | if (shouldBuildSpa) 15 | execSync('npm install && npm run build:app', { 16 | cwd: resolve('..', '..'), 17 | stdio: 'inherit', 18 | }) 19 | else await waitForAppToExist() 20 | 21 | buildSSRBundle() 22 | } 23 | 24 | async function waitForAppToExist() { 25 | while (!existsSync(script)) { 26 | console.log(`checking if "${script}" exists`) 27 | await new Promise(r => setTimeout(r, 2000)) 28 | } 29 | console.log(`found "${script}"`) 30 | } 31 | 32 | async function buildSSRBundle() { 33 | const bundle = await rollup({ 34 | input: script, 35 | inlineDynamicImports: true, 36 | }) 37 | await bundle.write({ format: 'umd', file: bundlePath, name: 'roxi-ssr' }) 38 | } 39 | -------------------------------------------------------------------------------- /fragments/vercel/template/api/vercel-ssr/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const { tossr } = require('tossr') 3 | 4 | const script = fs.readFileSync( 5 | require.resolve('../../dist/build/bundle.js'), 6 | 'utf8', 7 | ) 8 | const template = fs.readFileSync( 9 | require.resolve('../../dist/__app.html'), 10 | 'utf8', 11 | ) 12 | 13 | module.exports = async (req, res) => { 14 | const html = await tossr(template, script, req.url, {}) 15 | res.send(html + '\n') 16 | } 17 | -------------------------------------------------------------------------------- /fragments/vercel/template/api/vercel-ssr/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "vercel-build": "node ./build.js" 4 | }, 5 | "devDependencies": { 6 | "rollup": "^2.28.2" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /fragments/vercel/template/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "functions": { 4 | "api/vercel-ssr/index.js": { 5 | "includeFiles": "dist/**" 6 | } 7 | }, 8 | "routes": [ 9 | { 10 | "handle": "filesystem" 11 | }, 12 | { 13 | "src": "/.*", 14 | "dest": "/api/vercel-ssr/index.js" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /fragments/vite/blueprint.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** @type {import('canvasit')['Blueprint']} */ 4 | module.exports = { 5 | default: true, 6 | imports: { 7 | svelte: ['@sveltejs/vite-plugin-svelte', 'svelte'], 8 | resolve: ['path', 'resolve'], 9 | port: ['./package.json', 'appConfig', 'port'], 10 | viteMainJs: ['vite-main-js'], 11 | }, 12 | type: 'bundler', 13 | configs: ({ getConfigString, $require }) => ({ 14 | packagejson: require('./package.json'), 15 | svelte: { 16 | emitCss: 'true', 17 | hot: '!production', 18 | }, 19 | vite: { 20 | server: { 21 | port: $require('port'), 22 | }, 23 | build: { 24 | // remove this if you're not using tossr, spassr or spank 25 | polyfillModulePreload: 'false', 26 | cssCodeSplit: 'false' 27 | }, 28 | optimizeDeps: { 29 | exclude: ["'@roxi/routify'"], 30 | }, 31 | resolve: { 32 | dedupe: ["'@roxi/routify'"], 33 | }, 34 | plugins: [ 35 | $require('viteMainJs')(), 36 | $require('svelte')(getConfigString('svelte')), 37 | ], 38 | }, 39 | }), 40 | hooks: { 41 | afterConfig: ctx => { 42 | delete ctx.configs.packagejson.spassr 43 | delete ctx.configs.packagejson.spank 44 | }, 45 | afterPatch: ctx => { 46 | ctx.moveFile('static/__app.html', 'index.html') 47 | ctx.moveFile('static', 'public') 48 | const sviteParts = ctx.parseImports(ctx.stringify(ctx.configs.vite)) 49 | ctx.writeTo( 50 | 'vite.config.js', 51 | ` 52 | ${sviteParts.imports.join('\n')} 53 | ${sviteParts.declarations.join('\n')} 54 | const production = process.env.NODE_ENV === 'production' 55 | module.exports = ${sviteParts.body} 56 | `, 57 | ) 58 | }, 59 | }, 60 | } 61 | -------------------------------------------------------------------------------- /fragments/vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dev": "run-p dev:*", 4 | "dev:vite": "vite", 5 | "build:vite": "vite build", 6 | "serve:vite": "vite preview" 7 | }, 8 | "devDependencies": { 9 | "@sveltejs/vite-plugin-svelte": "^1.0.0-next.10", 10 | "npm-run-all": "^4.1.5", 11 | "svelte-hmr": "^0.14.4", 12 | "vite": "^2.3.7", 13 | "vite-main-js": "^0.0.1" 14 | }, 15 | "appConfig": { 16 | "script": "dist/main.js", 17 | "buildDir": "dist/assets", 18 | "assetsDir": "public", 19 | "template": "dist/index.html" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /fragments/vite/template/src/pages/guide/vite.md: -------------------------------------------------------------------------------- 1 | ### Vite 2 | 3 | Lightning fast (no)bundler. Server for modules in development, uses Rollup for production builds. Rollup plugins compatible. 4 | -------------------------------------------------------------------------------- /fragments/vite/template/static/__app.html.fragment.js: -------------------------------------------------------------------------------- 1 | exports.patch = ({ placeholders, configs, stringify }) => { 2 | placeholders.body.push('') 3 | } 4 | -------------------------------------------------------------------------------- /fragments/windicss/blueprint.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** @type {import('canvasit')['Blueprint']} */ 4 | module.exports = { 5 | type: 'feature', 6 | imports: { 7 | svelteWindicss: ['svelte-windicss-preprocess', 'preprocess'], 8 | }, 9 | configs: ({ getConfigString, $require }) => ({ 10 | windicss: { 11 | compile: 'false', 12 | prefix: '"windi-"', 13 | globalPreflight: 'true', 14 | globalUtility: 'true', 15 | }, 16 | svelte: { 17 | preprocess: [ 18 | $require('svelteWindicss')(getConfigString('windicss')), 19 | ], 20 | }, 21 | packagejson: require('./package.json'), 22 | }), 23 | } 24 | -------------------------------------------------------------------------------- /fragments/windicss/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "svelte-windicss-preprocess": "^3.0.0-beta.8" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /fragments/windicss/template/src/pages/guide/windicss.svelte: -------------------------------------------------------------------------------- 1 |

    Windicss

    2 |

    Check my style...

    3 | 4 | 9 | -------------------------------------------------------------------------------- /lib/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { program } = require('commander') 4 | const { merge } = require('canvasit') 5 | const prompts = require('prompts') 6 | const { resolve } = require('path') 7 | const { promptFragments } = require('./prompts') 8 | 9 | const commaSeparatedList = v => v.split(/[ ,]/) 10 | 11 | program 12 | .arguments('[name]') 13 | .option('-f, --fragments ', 'comma separated list of fragments to be used', commaSeparatedList) 14 | .action(async (name, options, command) => { 15 | let { fragments } = options 16 | 17 | fragments = fragments || await promptFragments() 18 | 19 | name = name 20 | || (await prompts({ 21 | type: 'text', 22 | message: 'type the folder where you would like to install', 23 | name: 'name', 24 | initial: ['routify', ...fragments, 'app'].join('-') 25 | })).name 26 | 27 | await merge( 28 | fragments, 29 | resolve(process.cwd(), name), 30 | { 31 | basepath: resolve(__dirname, '..', 'fragments'), 32 | ignore: [ 33 | "node_modules", 34 | "dist", 35 | ".routify" 36 | ], 37 | include: [ 38 | "base" 39 | ], 40 | prettierPlugins: [require.resolve('prettier-plugin-svelte')] 41 | } 42 | ) 43 | }) 44 | .parse() 45 | -------------------------------------------------------------------------------- /lib/generate-combos.js: -------------------------------------------------------------------------------- 1 | 2 | const { resolve } = require('path') 3 | 4 | const combinations = [ 5 | 'rollup, scroll-navigation, dev, milligram', 6 | 'rollup', 7 | 'nollup', 8 | 'nollup, spank, vercel', 9 | 'nollup, docker-ssr, spank', 10 | 'nollup, tailwindcss, spank', 11 | 'snowpack', 12 | 'rollup, i18n, navigation, content, milligram, markdown, auth' 13 | ] 14 | 15 | combinations.forEach(fragments => { 16 | fragments = fragments.split(/, */) 17 | Object.keys(require.cache).forEach(key => delete require.cache[key]) 18 | const dest = resolve('templates', fragments.join('-')) 19 | const { merge } = require('canvasit') 20 | merge(fragments, dest) 21 | }) -------------------------------------------------------------------------------- /lib/prompts.js: -------------------------------------------------------------------------------- 1 | const prompts = require('prompts'); 2 | const { readdirSync } = require('fs'); 3 | const { resolve } = require('path'); 4 | 5 | const FRAGMENTS_PATH = resolve(__dirname, '..', 'fragments') 6 | 7 | const TYPES = { 8 | bundler: { message: 'Pick a bundler. Combine at your own risk.' }, 9 | template: { message: 'Pick a template. Combine at your own risk.' }, 10 | feature: { message: 'Pick features.' }, 11 | content: { message: 'Pick content.' }, 12 | } 13 | 14 | const getBlueprintFromPath = (fragment) => ({ 15 | path: fragment, 16 | ...require(resolve(FRAGMENTS_PATH, fragment, 'blueprint.js')) 17 | }) 18 | 19 | const groupBlueprintsByType = (types, fragment) => { 20 | if (fragment.type !== 'base') { 21 | const type = fragment.type || 'other' 22 | types[type] = types[type] || {} 23 | types[type].fragments = types[type].fragments || [] 24 | types[type].fragments.push(fragment) 25 | } 26 | return types 27 | } 28 | 29 | const promptFragments = async () => { 30 | const fragmentPaths = [] 31 | const fragmentByTypes = readdirSync(FRAGMENTS_PATH) 32 | .map(getBlueprintFromPath) 33 | .reduce(groupBlueprintsByType, TYPES) 34 | 35 | for (const [name, type] of Object.entries(fragmentByTypes)) { 36 | const result = await prompts({ 37 | name: 'fragmentPaths', 38 | type: 'multiselect', 39 | instructions: false, 40 | message: type.message || 'Misc', 41 | choices: type.fragments.map(({ name, path, default: selected }) => ({ title: name || path, value: path, selected })), 42 | }) 43 | fragmentPaths.push(...result.fragmentPaths) 44 | } 45 | return fragmentPaths 46 | } 47 | 48 | module.exports = { promptFragments } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stackmix", 3 | "version": "0.1.8", 4 | "description": "", 5 | "main": "index.js", 6 | "bin": "lib/cli.js", 7 | "scripts": { 8 | "dev": "canvasit --watch -output output", 9 | "help": "canvasit help", 10 | "generate": "canvasit -output output", 11 | "test": "node test", 12 | "test:output:dev": "cd output && npm run test:dev", 13 | "test:output:build": "cd output && npm run test:build", 14 | "build": "node lib/generate-combos" 15 | }, 16 | "author": "", 17 | "license": "ISC", 18 | "devDependencies": { 19 | "ava": "^3.15.0", 20 | "kleur": "^3.0.3", 21 | "npm": "^7.6.3", 22 | "playwright": "^1.10.0", 23 | "prettier": "^2.3.0" 24 | }, 25 | "dependencies": { 26 | "canvasit": "^0.9.1", 27 | "commander": "^7.2.0", 28 | "markdown-it": "^12.0.4", 29 | "prettier-plugin-svelte": "^2.1.0", 30 | "prompts": "^2.4.0", 31 | "svelte": "^3.31.2" 32 | }, 33 | "canvasit": { 34 | "include": [ 35 | "base" 36 | ], 37 | "basepath": "fragments" 38 | }, 39 | "prettier": { 40 | "singleQuote": true, 41 | "quoteProps": "as-needed", 42 | "trailingComma": "all", 43 | "bracketSpacing": true, 44 | "arrowParens": "avoid", 45 | "semi": false, 46 | "useTabs": false, 47 | "tabWidth": 4, 48 | "svelteSortOrder": "options-scripts-markup-styles", 49 | "svelteAllowShorthand": true, 50 | "svelteBracketNewLine": false, 51 | "svelteIndentScriptAndStyle": true 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const prompts = require('prompts'); 2 | const { writeFileSync, existsSync, mkdirSync, rmdirSync, readdirSync, statSync } = require('fs') 3 | const { resolve } = require('path') 4 | const { spawnSync, spawn, execFileSync, execSync } = require('child_process') 5 | const kleur = require('kleur') 6 | 7 | 8 | const fragmentCombos = [ 9 | 'rollup,auth,content,i18n,milligram,markdown,spank', 10 | 'vite', 11 | 'nollup', 12 | 'snowpack' 13 | ] 14 | 15 | 16 | 17 | const npm = /^win/.test(process.platform) ? 'npm.cmd' : 'npm' 18 | const npx = /^win/.test(process.platform) ? 'npx.cmd' : 'npx' 19 | const canvasit = /^win/.test(process.platform) ? 'canvasit.cmd' : 'canvasit' 20 | const stdio = 'inherit' 21 | 22 | const workspaceDir = resolve(__dirname, 'workspace') 23 | removeTemplatesFromWorkspaceDir(workspaceDir) 24 | writeFileSync(resolve(workspaceDir, 'package.json'), '{"workspaces": ["*"]}') 25 | 26 | 27 | const children = fragmentCombos.map(async combo => { 28 | const name = combo.replace(/,/g, '-') 29 | const outputDir = resolve(__dirname, 'workspace', name) 30 | const templatePkgJsonPath = resolve(outputDir, 'package.json') 31 | const child = spawn(canvasit, ['-output', outputDir, '-fragments', combo], { stdio }) 32 | await new Promise(resolve => child.on('close', resolve)) 33 | const pkgjson = require(templatePkgJsonPath) 34 | pkgjson.name = `test-${name}` 35 | writeFileSync(templatePkgJsonPath, JSON.stringify(pkgjson, null, 2)) 36 | console.log(`[test] testing combos:`, combo) 37 | }) 38 | 39 | Promise.all(children).then(() => { 40 | console.log('[test] Installing NPN dependencies') 41 | spawnSync(npm, ['install'], { cwd: workspaceDir, stdio }) 42 | console.log('[test] Installed NPM dependencies') 43 | for (const combo of fragmentCombos) { 44 | const msg = kleur.bold(`| TEST: ${kleur.yellow(combo)} |`) 45 | const length = msg.length - 20 46 | console.log(`+${new Array(length).join('-')}+`) 47 | console.log(`|${new Array(length).join(' ')}|`) 48 | console.log(msg) 49 | console.log(`|${new Array(length).join(' ')}|`) 50 | console.log(`+${new Array(length).join('-')}+`) 51 | spawnSync(npm, ['test'], { cwd: resolve('test', 'workspace', combo.replace(/,/g, '-')), stdio }) 52 | } 53 | }) 54 | 55 | 56 | function removeTemplatesFromWorkspaceDir(dir) { 57 | if (existsSync(dir)) { 58 | readdirSync(dir) 59 | .filter(file => file !== 'node_modules') 60 | .map(file => resolve(dir, file)) 61 | .filter(filepath => statSync(filepath).isDirectory()) 62 | .forEach(filepath => rmdirSync(filepath, { recursive: true })) 63 | } 64 | else 65 | mkdirSync(dir) 66 | } --------------------------------------------------------------------------------