Giter Site home page Giter Site logo

vitreum's Introduction

vitreum

An opinioned build system for modern web apps.

npm version

vitreum is a build tool for creating web applications; similar to webpack and parcel. It uses common tools: React, Browserify, and LiveReload. vitreum focuses on incredibly fast build times and tooling for tightly active development.

Example build script for your project

const fs = require('fs-extra');
const { pack } = require('vitreum');

let cssCache = {}
const transforms = {
  '.css' : (code, filename)=>{
    cssCache[filename] = code;
    return;
  }
};

const renderHTML = ({head, body, props})=>{
  return `<html lang='en'>
  <head>
    ${head}
  </head>
  <body>
    <main>${body}</main>
  </body>
  <script src='/bundle.js'></script>
  <script>start_app(${JSON.stringify(props)})</script>
</html>`
};


const build = async ()=>{
  const { bundle, render } = await pack('../client/main.jsx', {
    transforms,
    libs : ['very_big_lib.js']
  });

  const props = { msg : 'hello world!'};

  await fs.outputFile('./build/bundle.js', bundle);
  await fs.outputFile('./build/index.html'), renderHTML({
    head: `<style>${Object.values(cssCache).join('\n')}</style>`
    body : render(props),
    props
  });
};

build();

_ In this example we are bundling a component and producing a bundle.js and a index.html with the component pre-rendered with the given props. The start() function will kick off the React app. We've added a custom transform to be able to require in CSS files and have them rendered into the <head> of the HTML. And lastly, there's a very large library that will be included, but the transforms will not be applied to it. _

How it works

Vitreums goal is to take a path to a react component, process and bundle it, then return three things:

  1. bundle: Client-side friendly bundle of all needed code to run it
  2. render: A function to render a string html version of the component with given opts.
  3. ssr: Code for server-side rendering this component on request.

It's up to you how'd you like to use these pieces of contruct your app's specific build process. Maybe bundles everything together into a single HTML file and upload it to S3? Easily add a react-powered github page to your repo? Use transforms to bundle everything into a chrome extension and zip it? Entirely up to you!

Features

  • Custom Transforms: Control how vitreum loads and processes specific filetypes. Allowing you to require images, css files, etc. all within your bundle.
  • Dev Mode: Vitreum can watch your file system and rebundle only the files that change while developing

Recipes

Since each project has different build needs vitreum functions more like a toolbox, exposing useful tools to use to craft the build system you need. Vitreum doesn't work right out-of-box, and it's expected that you have a build script written for your project. This way as your project changes you can tweak and modify the process easily without needing to use a bunch of plugins or 3rd party tooling.

Vitreum comes with several examples, or "recipes" for common build flows. Most of the time you can just copy and paste one of them and you're good to go.

[Link to recipes here]

API

async vitreum.pack(path_to_component, [opts]) -> {bundle, render, ssr}

Takes a path_to_component, and any additional options, and async returns bundle, render, ssr.

  • bundle : A string of javascript that is all the code needed to execute your app client-side (including React and libraries). It exposes a single global function start(props, target) that will start your webapp with the props provided and targeting a specific DOM node.
  • render(props): A function that when called with props will return a string of HTML of your webapp rendered with the given props.
  • ssr: A string of javascript that will expose a function for doing server-side rendering (essentially render()). Save this and require it in your server to dynamically server-side render your app on request.
const fs = require('fs').promises;
const { pack, html } = require('vitreum');

const run = async ()=>{
  const { bundle, render, ssr } = pack('./client/main.jsx', {
    libs : ['very_large_lib.js'], // Won't have transforms applied to it
    paths : ['./client/shared']   // option passed to Browserify
  });

  const mainPage = html({
    body : render();
    tail : `<script src='./bundle.js'></script>`
  });
  await fs.writeFile('./build/index.html', mainPage);
  await fs.writeFile('./build/bundle.js', bundle);
  console.log('Done!');
}

run();
//Opts
{
  dev : ({bundle, render, ssr})=>{}, //If provided will put vitreum in dev mode, watching for file changes. Will call the function with the new results of `pack` on change
  transforms : {}, //Any custom transforms, key should be file extension, and value is a transform function
  libs : ['foo', 'big_lib'], //Any libraries or files that should not have transforms applied to them. Usually for large and/or complex dependacies
  ...rest, //Any additional opts will be passed directly to Browserify as options, https://github.com/browserify/browserify#browserifyfiles--opts
}

vitreum.html({head, body, tail, props}) -> string of html

Provides a very basic html template renderer if you project doesn't need anything special. Copy this file into your project and make changes to it if you'd like more control over how your HTML is rendered.

  • head: HTML string to be injected in the head of your html
  • body: HTML string to be injected into the body of your html, wrapped in a <main> tag
  • tail: HTML string to be injected at the end of your HTML
  • props: A javascript object that will be stringified an embedded in the start_app() call

Note: vitreum.html will automatically add a startup script to the tail of your HTML. This kicks off React and starts your webapp. If you are generating your own HTML, make sure you copy over this functionality.

const fs = require('fs').promises;
const { pack, html } = require('vitreum');

pack('./client/main.jsx')
  .then(({render})=>{
    return fs.writeFile('./build/index.html', html({
      head : `<title>My Project</title>`,
      body : render();
    }))
  });

vitreum.server(path_to_folder, [opts]) -> Launches a simple server

Sometimes you just need a really simple server to host your project for development purposes. vitreum ships with one that does just that.

Note: For dev-purposes only, use a more mature solution for production.

const { server } = require('vitreum');

// Use vitreum to bundle your project here...

//Then kick off the server!
server('./build', {
  port : 8001,
})
//Opts
{
  port : 8000, //What port the server should run on
  basepath : '/foo', //Always ensures every URL is prefexed with this base_path. Used for testing github pages mainly.
}

vitreum.livereload(watch_path)

Sets up a LiveReload watching the watch_path. Whenever a file changes there, sends a signal from the LiveReload server. If you have the extension installed it will automatically update the source on your page. Incredibly usage for developing.

const { livereload, server } = require('vitreum');

// Bundle code here...

server('./build');
livereload('./build'); // Will update whenever a file is changed in the build folder

vitreum.utils

A collection of little utilities that vitreum uses.

  • chalk(msg): a micro-implementation of chalk, used to color console messages
  • relativePath(path, offset=1): Returns an absolute path using the passed in relative path, and a stacktrace offset.
  • decache(path): de-caches a file from Node's require
  • writeFile(path, content): A simple utility function that combines fs-extra.ensureFile and fs.writeFile. It will create any needed directory structure inorder to successfully create/update the file.

Transforms

Transforms are Browserify's way of processing certain files before they get bundled. This can be leveraged in powerful ways, such as being able to require in text files as strings, or automatically copying assets to a build folder and getting the new path to it as a string in code.

vitreum exposes a simple iterface for writing your own transforms, along with some useful builtin ones you can use.

A transform consists of an extension association and a transforming function, while will be passed the file's path and contents.

const foo2js = require('foo2js');
const fse  = require('fs-extra');

const transforms = {
  '.foo': (code, filename, opts)=>{
    return foor2js(code);
  },
  '*': async (code, filename, opts)=>{
    const newPath = path.relative(opts.entrypoint, filename);
    await fse.copy(filename, './build/' + newPath);
    return `module.exports= '${newPath}';`
  }
}

In this example we have two custom transforms. The first is leveraging a third-party module to transform .foo files into javascript. The second transform is applied to any file that doesn't match the other transforms, and will copy them to the build directory and return the new path to it as a string.

Built-in Transforms

[list will go here]

  • style
  • asset
  • yaml
  • encode

Default Transforms

vitreum comes with 4 default transforms applied. These can be overwritten if you want.

const babelify = async (code)=>(await require('@babel/core').transformAsync(code,{ presets : ['@babel/preset-react'] })).code;

const defaultTransforms = {
  '.js'   : (code, filename, opts)=>babelify(code),
  '.jsx'  : (code, filename, opts)=>babelify(code),
  '.json' : (code, filename, opts)=>`module.exports=${code};`,
  '*'     : (code, filename, opts)=>`module.exports=\`${code}\`;`
};

vitreum's People

Contributors

ktrain avatar stolksdorf avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

vitreum's Issues

Meta headtags don't generate anything on SSR

I'm not sure why the headtags are restricted to do anything "onServer", but I haven't found a good way to make them actually generate header tags except for the Title which directly edits the DOM.

Title({ children }){
	if(onServer) NamedTags.title = `<title>${children.join('')}</title>`;
	React.useEffect(()=>{document.title = children.join('')}, [children]); <====
	return null;
},

Might I suggest removing the "onServer" check and adding an Event listener? Otherwise all of the updated props seem to get overwritten on Hydration.

Meta(props) {
  let tag = `<meta ${obj2props(props)} />`;
  props.property || props.name ? NamedTags[props.property || props.name] = tag : UnnamedTags.push(tag);

  React.useEffect(() => {
    document.getElementsByTagName('head')[0].insertAdjacentHTML('beforeend', Object.values(NamedTags).join('\n'));
  },[NamedTags]);
  return null;
},

Otherwise if headtags could be added to the pack output so we can insert them into our template function.

Bunch of things to fix

init

  • add in a client.init.js
  • Remove func from eslint
  • Add title and favicon into main.jsx
  • Add router into main.jsx
  • Add node_modules` to eslint ignore

other

  • change bundle opt to packagesToTransform
  • Add a fullEmbed option into the render function

Naturalcrit access

@stolksdorf I know this is a dumb way to do it, but I can't figure out a good way to contact you otherwise. Can you come by the gitter.im chat or send an email? I'm trying to make improvements to the Homebrewery but without access to Naturalcrit I'm stuck.

Build fails on Heroku when including headtags in project libs

If vitreum/headtags is specified in project libs, then npm run build as a postinstall hook will fail on Heroku with a parse error.

project.json

{
  "entryPoints": {
    "main": "./client/main/main.jsx"
  },
  "clientDir": "./client",
  "assets": [],
  "libs": [
    "react",
    "react-dom",
    "lodash",
    "classnames",
    "pico-flux",
    "superagent",
    "superagent-promise"
  ]
}

Output on deployment

remote: -----> Building dependencies
remote:        Installing node modules (package.json)
remote:        
remote:        > [email protected] postinstall /tmp/build_a768d8d2cc00271c32dc131f7b437c49
remote:        > npm run build
remote:        
remote:        
remote:        > [email protected] build /tmp/build_a768d8d2cc00271c32dc131f7b437c49
remote:        > node scripts/build.js
remote:        
remote:        clean...
remote:        clean            ✓ 36ms
remote:        libs...
remote:        JS_Parse_Error {
remote:        message: 'SyntaxError: Unexpected token: operator (>)',
remote:        filename: 0,
remote:        line: 21597,
remote:        col: 22,
remote:        pos: 742753,
remote:        stack: 'Errorn    at new JS_Parse_Error (eval at <anonymous> (/tmp/build_a768d8d2cc00271c32dc131f7b437c49/node_modules/uglify-js/tools/node.js:28:1), <anonymous>:1547:18)n    at js_error (eval at <anonymous> (/tmp/build_a768d8d2cc00271c32dc131f7b437c49/node_modules/uglify-js/tools/node.js:28:1), <anonymous>:1555:11)n    at croak (eval at <anonymous> (/tmp/build_a768d8d2cc00271c32dc131f7b437c49/node_modules/uglify-js/tools/node.js:28:1), <anonymous>:2094:9)n    at token_error (eval at <anonymous> (/tmp/build_a768d8d2cc00271c32dc131f7b437c49/node_modules/uglify-js/tools/node.js:28:1), <anonymous>:2102:9)n    at unexpected (eval at <anonymous> (/tmp/build_a768d8d2cc00271c32dc131f7b437c49/node_modules/uglify-js/tools/node.js:28:1), <anonymous>:2108:9)n    at expr_atom (eval at <anonymous> (/tmp/build_a768d8d2cc00271c32dc131f7b437c49/node_modules/uglify-js/tools/node.js:28:1), <anonymous>:2635:9)n    at maybe_unary (eval at <anonymous> (/tmp/build_a768d8d2cc00271c32dc131f7b437c49/node_modules/uglify-js/tools/node.js:28:1), <anonymous>:2797:19)n    at expr_ops (eval at <anonymous> (/tmp/build_a768d8d2cc00271c32dc131f7b437c49/node_modules/uglify-js/tools/node.js:28:1), <anonymous>:2832:24)n    at maybe_conditional (eval at <anonymous> (/tmp/build_a768d8d2cc00271c32dc131f7b437c49/node_modules/uglify-js/tools/node.js:28:1), <anonymous>:2837:20)n    at maybe_assign (eval at <anonymous> (/tmp/build_a768d8d2cc00271c32dc131f7b437c49/node_modules/uglify-js/tools/node.js:28:1), <anonymous>:2861:20)' }
remote:        libs             ✓ 2867ms

Add ability to require in all steps as an object

Add an index.js in steps that returns an object with all of them. See if you'll need a separate require for just the dev steps, (shouldn't need to if the require statements are contained within the function calls)

Throws bad errors when requires are not found

If you are trying to require something that doesn't exist, the Vitruem error handler will give back a pretty bad error, not listing the file and giving the live/column as undefined:undefined.

Allow resolving modules with configurable extensions

Currently, vitreum automatically resolves files with .js and .json extensions as Javascript modules.
It would be nice if this could be configurable to:

  • Allow other typical file extensions, like .jsx
  • Disallow all automatic resolution

One way of solving this would be to allow passing any browserify options rather than picking just presets and global as overridable options.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.