- Project Overview
- Project Structure
- Workflow Overview
- Detailed Workflow Instructions
- Brief Guidelines for Components
This is a Svelte repository for reusable, framework-agnostic web components for use inside Shorthand and other CSIS digital projects. The components are Svelte 5 custom elements. Each element compiles into an HTML tag that can be used in any page that allows custom HTML and JavaScript.
All production components are bundled into one file, dist/widgets.js which is tagged and released using SemVer. In development, we use dist/widgets-dev.js. It is only built inside a development branch and never committed to main.
To use the components, include <script> tag that loads the library from our CDN. We use jsDelivr. This README outlines the required workflow for development and production relases, ensuring users always load the correct version and preventing experimental components from being included in production builds.
This repository is developer-facing only. Non-developers should only interact with the code via the component tags included in production releases.
/dist ← compiled bundles (do **NOT** edit manually)
widgets.js ← bundle and release on **main** only
widgets-dev.js ← dev branches **only**
/src
/lib ← production-ready components, in widgets.js
/dev ← experimental components, in widgets-dev.js only
dev-main.js ← dev entry point, imports lib && dev
main.js ← production entry point, imports lib only
.gitignore
.index.html ← use for local dev, import main.js or dev-main.js
package.json
README.md
svelte.config.js
vite.config.js
widgets.js and widgets-dev.js are the distribution files. widgets.js contains only what is inside the src/lib folder, and widgets-dev.js contains what is inside both src/lib and src/dev.
The separation between dev and lib serves both to keep things clean and as a safeguard.
- The
devdirectory may contain any components that are under development/not ready for production. libshould only contain what will be in the production library and released for use in Shorthand (or elsewhere).
When a component is ready to be promoted from dev to production, it should be copied to lib and deleted from dev. Most frequently, dev components will stay in development branches and never be merged into main.
dev is a directory that you create on your branch; it shouldn't be on main. BUT, a dev component may end up on main:
- By accident - we forget to check the
devdirectory when reviewing a PR, focusing only on what's going to prod - To experiment - we may want a component in
devto be made available for everyone to check out and iterate on/implement new features.
The most important part: lib is production only.
main.js and dev-main.js are set up to automatically import and register the components.
They are configured to recognize *.ce.svelte as the component extension and will NOT import files with any other extension. This is to separate components from their helper files.
Any helpers should be given the regular *.svelte extension (or whatever other appropriate extension).
The index.html file is purely used for experimentation during development. This repo is not deployed, and index.html is not included in the production distro file.
<script type="module" src="/src/dev-main.js"></script> will load all components, both dev and production.
<script type="module" src="/src/main.js"></script> will load only production components.
Start the dev server and then inside <body>, use your new component and test changing the various attributes. A component should render cleanly and all attributes should function before it is tested inside Shorthand (and should work in Shorthand before merged into main).
- name: same as repository
private: true- package can't accidentally be published to npmtype:module- treatjsfiles as ES modules so we can useimportand modern tooling- scripts:
- dev:
npm run devstarts local server atlocalhost:5173and loads what's inindex.html(/src/main.jsor/src/dev-main.js); supports hot-reloading, - build:
npm run buildwill builddist/widgets.jsfor production - build:dev-bundle:
npm run build:dev-bundlewill builddist/widgets-dev.jsfor development use only - preview:
npm run previewwill start a local static server you can use to test what's indist. This will simulate what you'll get from the CDN. Inindex.html, replace the<script>importing fromsrcwith one that imports/dist/widgets.jsor/dist/widgets.js.
- dev:
- author: the iLab
- license:
UNLICENSEDindicates that the code is for CSIS use only - description: "Svelte-based Web Components used across CSIS iDeas Lab projects".
- devDependencies: required only during development and build time
@sveltejs/vite-plugin-svelte- the official Svelte plugin for vite; handles compiling the components, enabling hot reload, optimization, generating CustomElementssvelte- the Svelte frameworkvite- the development server and build tool
- dependencies: included in final bundle and available to components at runtime
- gsap - animation library. Note: a component must import GSAP for it to be included in the component's compiled output.
This file configures how Svelte compiles components in this project. customElement: true tells svelte to compile every .svelte file (or .ce.svelte, in our case) as a Web Component instead of a normal Svelte component. This is what makes the repo function as a Web Component Library instead of a regular Svelte website/app project.
import { vitePreprocess } from @sveltejs/vite-plugin-svelte'; maintains compatability with Svelte/Vite tooling and allows for future use of tools like SCSS and TypeScript.
This file controls how Vite builds the component library. It produces two different bundles: a production bundle, widgets.js, and a dev testing bundle, widgets-dev.js
mode- lets the config switch based on build typeentry- selects the appropriate js file based on modeformats- IIFE creates an immediately invoked function expression. We get a single, self-running JS file with no other exports, imports, or bundler required and has no dependencies on Vite or Svelte at runtime.fileName- the names of the bundles createdrollupOptions- no code-splitting, everything in one file, Shorthand only needs one<script>tag
Detailed instructions are below (and must be followed for this to work properly) but the general idea is here:
- Clone the repo, run
npm install, start the dev server, check thatlocalhost:5173is servingindex.html. - Create a branch for your component - all dev work goes here. If you're working on multiple, each gets its own branch.
- In
dev/, create and build your component.- SINGLE FILE components stay in
dev/ - MULTI FILE components get their own folder:
dev/component-name/
- SINGLE FILE components stay in
- In
index.htmlmake sure you're importingsrc/main-dev.jsand check your component using localhost - Once your component works locally, run
npm run build:dev-bundleto generatedist/widgets-dev.js. - Get the SHA for that commit to use in the CDN link, put the
<script>in the Shorthand settings and test in Shorthand - Once that works, move your component from
devtoliband make your PR - PR Reviewers: confirm things work in Shorthand, then delete
widgets-dev.jsand approve the PR - On approval, merge the PR and create a release
- On
mainONLY, runnpm run build, create a new commit for the updated file, use the new version number for the commit message, push the commit. - Once the commit is pushed, create and push a tag using that same version number.
- On
Say we want to build cool-component:
- Make sure you have Node 20+ and npm
- Clone repo down locally
- From
main, create and checkout a branch for your component, likefeat/cool-component.- A branch is for dev of a single component. If you have others, they'll need their own branches.
- Run
npm install - Run
npm run devto get the local server running. Checklocalhost:5173to make sure that what's inindex.htmlis running.
- In
src/dev, createCoolComponent.ce.svelte- A single file component like
CoolComponent.ce.sveltegoes directly insidedev/ - A multi file component gets its own directory inside
dev/, e.g.dev/complex-component.
- A single file component like
- In
index.html, make sure you're using<script type="module" src="/src/dev-main.js"></script>so it pulls from thedevdirectory - Work on your component, use localhost to check that it's working
- Once you're happy with your component, run
npm run build:dev-bundle. This createsdist/widgets-dev.js.- This command should only ever be run in a dev branch, never on
main.
- This command should only ever be run in a dev branch, never on
- Commit and push your changes to your branch,
feat/cool-component.
- Once you've pushed your component to your branch, run
git rev-parse HEADto get the SHA of the last commit you made (the one that pushed your changes to GitHub). It'll look like a giant string,9c1e1a7f8f3827b11fb9eb34ab0a21afde30e19c. - In Shorthand, add the CDN
<script>tag to the story's settings, down under "CUSTOM<HEAD>".- You want to put your SHA after the @ in the link.
- The formula is:
<script src="https://cdn.jsdelivr.net/gh/CSIS-iLab/ilab-web-components@YOUR_SHA_HERE/dist/widgets-dev.js"></script>
- From here, you can use your component in any custom HTML section by using its registered name, e.g.
<csis-cool-component>.
- jsDelivr caches incredibly hard, so it won't automatically grab the newest version of your file. (We also don't want it to, to protect stories from breaking as the library develops.)
- Using the SHA forces jsDelivr to grab the file as it is in that specific commit.
- Avoids needing to create/maintain tags; these are throwaway dev-only files.
- If you're ready for your component to move into production, move your
*.ce.sveltefile FROMdevTOlib. - You do NOT need to run any build command. That will be done on
main.
- Once you're happy with how your component is performing in Shorthand, it's time to make a PR to merge your branch to
main. - Your PR should
- Describe your component AND define any attributes it has for documentation. These should be written down as they'd be used by producers/users (e.g. "text-color") not as they are in the JS (e.g. "textColor").
- e.g. "
text-color- The color of all text"
- e.g. "
- Include a screenshot of your component
- Include the SHA used to check your component in Shorthand
- Describe your component AND define any attributes it has for documentation. These should be written down as they'd be used by producers/users (e.g. "text-color") not as they are in the JS (e.g. "textColor").
- If you're reviewing someone else's PR, use the SHA they provide and double check that the component works in a Shorthand story.
- If it does, hooray! Delete
dist/widgets-dev.jsand approve the PR.
Once your PR is approved, it's time to merge and update the library!
- Merge your PR to
mainand delete the branch. - In your editor, checkout the
mainbranch and thengit pullto make sure you've got the newest version that includes your changes. - Run
npm run buildto generate the newdist/widgets.jsfile that incorporates your new component. - Run
git describe --tags --abbrev=0to see the most latest version tag. - Figure out what the next version should be using SemVer:
MAJOR.MINOR.PATCH- MAJOR version when there are breaking changes
- Examples: removing a component from the library, renaming a component, renaming a component attribute, changing default behavior that affects layout, renaming or removing CSS custom properties, changing visual defaults like text color or background color
- MINOR version when functionality is added and backward compatible
- Examples: adding new components
- PATCH version for bug fixes
- Examples: bug fixes, style corrections, internal refactoring, performance improvements, fixing typos in default text
- MAJOR version when there are breaking changes
- Create a commit for the new dist file using the new version as the message, e.g.:
git commit -m "Release v1.3.0" - Push your commit
- Tag your commit and push your tag
git tag v1.3.0git push --tags
And that's it! A new release has been created! Any Shorthand stories waiting on your new component should use the new version, and any documentation should be updated to reflect the new version.
The URL for your new release is: <script src="https://cdn.jsdelivr.net/gh/CSIS-iLab/ilab-web-components@VERSION_HERE/dist/widgets.js"></script> where VERSION_HERE is whatever the version was, v0.1.0 or v1.3.0 etc.
And old stories or web pages that are using an old version do not need to be updated. Because we're telling jsDelivr to reference specific commits by using tags, those old stories will always reference that specific working version of the widgets.js file. It will not be broken or updated by our future versions.
- Props should map cleanly to HTML attributes, using kebab case.
- Styles should stay in Shadow DOM. Avoid
:globalif at all possible. If you need it, scope it to your component::global(.my-component-wrapper a){...}. Never use unscoped:globalselectors likeaandp. - CSS customization should use component-specific custom properties (e.g.
--cpp-callout-bg)