Giter VIP home page Giter VIP logo

underreact's Introduction

DEPRECATED!

As of April 2022, we are deprecating Underreact and do not recommend usage on new projects. Development will only occur on an exemption basis for existing, internal Mapbox use-cases.

New projects should use an alternative such as Create React App or Vite.


@mapbox/underreact

Minimal, extensible React app build system that you won't need to eject.

It's a pretty thin wrapper around Babel, Webpack, and PostCSS, and will never accumulate an ecosystem of its own. And it aims to be just as useful for production applications with idiosyncratic demands as for simple prototypes.

Table of contents

Installation

Requirements:

  • Node 6+.

Install Underreact as a devDependency of your project:

npm install --save-dev @mapbox/underreact

If you are building a React application, you also need to install React dependencies:

npm install react react-dom

Add _underreact* to your .gitignore, and maybe other ignore files (e.g. .eslintignore). That way you'll ignore files that Underreact generates. (If you set the outputDirectory option, you'll want to ignore your custom output directory.)

Getting started

The bare minimum to get started

  • Create your entry JS file at src/index.js.
// src/index.js
console.log('hello world!');
  • Run the development server with underreact.
npx underreact start
# or
node node_modules/.bin/underreact start
  • Open the URL printed in your terminal.

Getting started with React

  • Create your entry JS file at src/index.js.
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {
  render() {
    return <div>Hello world</div>;
  }
}

const container = document.createElement('div');
document.body.appendChild(container);
ReactDOM.render(<App />, container);
  • Run the development server with underreact.
npx underreact start
# or
node node_modules/.bin/underreact start
  • Open the URL printed in your terminal.

Usage

You should not install the Underreact CLI globally. Instead, install Underreact as a devDependency of your project and use the underreact command via npx, npm "scripts", or node_modules/.bin/underreact. The easiest way is probably to set up npm scripts in package.json, so you can use npm run start, npm run build, etc., as needed.

The CLI provides the following commands:

  • start: Start a development server.
  • build: Build for deployment.
  • serve-static: Serve the files that you built for deployment.

Tip: In this readme we frequently use the command npx, if you find it unfamiliar please read this blog post by npm.

Underreact configuration file

To configure Underreact, create an underreact.config.js file at the root of your project.

Please note that no configuration is necessary to get started. On most production projects you'll want to set at least a few of the configuration object properties.

Your underreact.config.js can export a function or an object.

Exporting an object

You can also directly export the configuration object. This is a great way to start tweaking Underreact's configuration. For example, in the code below we simply modify the siteBasePath:

// underreact.config.js
module.exports = {
  siteBasePath: 'fancy'
};

Exporting a function

You can also export a function that returns your configuration object.

This function is called with the following named parameters:

// underreact.config.js
/**
 * @param {Object} opts
 * @param {Webpack} opts.webpack - Underreact's version of Webpack. Use this as needed to apply core Webpack plugins like `PrefetchPlugin`, `IgnorePlugin`, and `SourceMapDevToolPlugin`, so that your project is not dependent on its own Webpack version.
 * @param {'start'|'build'|'serve-static'} opts.command - The current Underreact command.
 * @param {'production'|'development'} opts.mode - The current mode of Underreact.
 * @returns {Promise<Object> | Object}
 */
module.exports = function underreactConfig({ webpack, command, mode }) {
  return {
    /* Underreact configuration object */
  };
};

This approach is quite powerful, because you can also return a Promise or use an async function to generate configurations with asynchronous dependencies from the filesystem or Internet. For example:

// underreact.config.js
const path = require('path');
const downloadAssets = require('./scripts/fetchAssets');

module.exports = async function underreactConfig({ webpack, command, mode }) {
  const publicAssetsPath = 'public';
  await downloadAssets(path.resolve(publicAssetsPath));

  return {
    publicAssetsPath,
    webpackPlugins: [command === 'build' ? new webpack.ProgressPlugin() : null]
  };
};

Defining your HTML

Underreact is intended for single-page apps, so you only need one HTML page. If you are building a React application, you can also use it to define a div element for react-dom to mount your React component tree on.

You have 2 choices:

  1. Preferred: Provide the htmlSource configuration option, which is an HTML string, a Promise or a Function returning HTML string or promise, that resolves to an HTML string.
  2. Provide no HTML-rendering function and let Underreact use the default, development-only HTML document. You should only do this for prototyping and early development: for production projects, you'll definitely want to define your own HTML, if only for the <title>.

If you provide a Promise for htmlSource, you can use any async I/O you need to put together the page. For example, you could read JS files and inject their code directly into <script> tags, or inject CSS into <style> tags. Or you could make an HTTP call to fetch dynamic data and inject it into the page with a <script> tag, so it's available to your React app.

If you provide a Function for htmlSource, Underreact would call it with the named parameter basePath. This gives you the flexibility to load assets with a root relative URL. The example below shows how to load a favicon from your public directory:

// underreact.config.js
module.exports = {
  /**
   * @param {Object} opts
   * @param {Webpack} opts.basePath - the normalized value of your site's base path
   * @returns {Promise<string> | string} 
   */
  htmlSource: ({ basePath }) => `
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="utf-8">
      <title>Words that rhyme with fish</title>
      <meta name="description" content="A website about words that rhyme with fish, like plish">
      <link rel="shortcut icon" href="${basePath}/img/favicon.ico" type="image/x-icon" />
    </head>
    <body>
      <div id="app">
        <!-- React app will be rendered into this div -->
      </div>
    </body>
    </html>
  `
};

Note: Underreact would automatically inject the relevant script and link tags to your HTML template.

In the example below, we are defining our HTML in a separate file and requiring it in underreact.config.js:

// underreact.config.js
const html = require('./html');

module.exports = function underreactConfig({ webpack, command, mode }) {
  return {
    htmlSource: html(mode)
  };
};

// html.js
const fs = require('fs');
const { promisify } = require('util');
const minimizeJs = require('./minimize-js');

module.exports = async mode => {
  // read an external script, which we will inline
  let inlineJs = await promisify(fs.readFile)('./path/to/some-script.js');

  if (mode === 'production') {
    inlineJs = minimizeJs(inlineJs);
  }

  return `
      <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="utf-8">
        <title>Words that rhyme with fish</title>
        <meta name="description" content="A website about words that rhyme with fish, like plish">
        <script>${inlineJs}</script>
      </head>
      <body>
        <div id="app">
          <!-- React app will be rendered into this div -->
        </div>
      </body>
      </html>
    `;
};

Modes

Underreact provides two different modes of execution: development and production

Development mode

The development mode is the default mode of the start command. This mode is meant to be used in a local development environment, ideally your computer. Underreact does a bunch of optimizations to make compilation as fast as possible and enable developer tools like hot reloading and live reloading.

You can use this mode by simply running underreact start:

npx underreact start
# or being explicit
npx underreact start --mode=development

You can also use this mode with the build command and then serve it with serve-static, if you want to perform quick inspection of unminified files.

npx underreact build --mode=development
# serve it
npx underreact serve-static

Warning: Do not host code generated by development mode in a production environment.

Production mode

This mode is geared towards running the build output in a production environment. Underreact performs a bunch of optimizations to make your application run fast and reduce the bundle size.

You can use this mode by simply running underreact build:

npx underreact build
# or being explicit
npx underreact build --mode=production

You can also use this mode with the start command, in case you need to debug a problem that does not show up in development mode (e.g. one caused by minification):

npx underreact start --mode=production

Babel

Out of the box Underreact doesn't require you to setup a babel.config.js file. It uses @mapbox/babel-preset-mapbox internally to provide a top-notch default configuration.

Exposing babel.config.js

There are many cases — for example, when using Jest — when you want a babel.config.js to exist at the root your project. In this case it is best to create a babel.config.js at the root of your project and install @mapbox/babel-preset-mapbox as a devDependency:

npm install --save-dev @mapbox/babel-preset-mapbox
// babel.config.js
module.exports = {
  presets: ['@mapbox/babel-preset-mapbox']
};

While you are free to use any Babel presets & plugins, we strongly recommend that you use @mapbox/babel-preset-mapbox, as it provides a good combination of presets and plugins that are necessary for any Underreact application to work properly. For more advanced configuration visit the documentation for @mapbox/babel-preset-mapbox.

Note: Underreact doesn't support .babelrc; please use babel.config.js. (Read more about the difference here).

Browser support and polyfills

One of the founding principles of the Internet is its ability to support a multitude of devices. With the ever changing JavaScript ecosystem, new features of the language coming yearly and it has become difficult to use them while also supporting older browsers. Underreact wraps tools that solve these problems for you.

Transpiling JavaScript syntax and vendor-prefixing CSS

In Underreact you can use the Browserslist notation to specify the browser versions that you want to support. By default, Underreact uses a query that supports all major browsers including ie 11. You can change this behaviour by customizing the browserslist property:

// underreact.config.js
module.exports = {
  // The % refers to the global coverage of users from browserslist
  browserslist: ['>0.25%', 'not ie 11']
};

In the example above we are setting browserslist to target all the browsers with greater than 0.25% market share but not IE 11. This information will be passed to Autoprefixer to add vendor prefixes to CSS and to Babel to transpile your JavaScript to ES5.

Polyfilling newer JavaScript features

By default, Underreact polyfills the following JavaScript features:

  • Array.from
  • Object.assign
  • Promise
  • Symbol

The above polyfills (combined with Babel's transpilation) allow you to freely use for..of loops, async functions, and the spread operator.

If your application needs any other polyfill (e.g. fetch), you can install it and import it at the top of your jsEntry file:

// src/index.js
import 'whatwg-fetch';

Using @babel/polyfill

If you don't care about bundle size and want to polyfill all standard JS, you can install @babel/polyfill and import it in your jsEntry file.

Warning: polyfill must be set to false to use @babel/polyfill and you should only import @babel/polyfill once and only once in your application.

Deployment environments

Using environment variables

Underreact allows you to inject environment variables into your client-side code at build time. You can set them up by using the environmentVariables option in your configuration.

// underreact.config.js
module.exports = {
  environmentVariables: {
    SERVER_URL: 'https://ketchup.com'
  }
};

Note: DEPLOY_ENV & NODE_ENV are special environment variables in Underreact, so cannot be set in Underreact configuration.

DEPLOY_ENV and NODE_ENV

  • NODE_ENV will default to 'development' in development mode and 'production' in production mode.
  • It's recommended that you not set NODE_ENV manually: use Underreact modes instead. But if you do set NODE_ENV, it must be 'development', 'production', or 'test'.
  • By default, DEPLOY_ENV is set to 'development'. You can it to any value you wish to better align with your target environments (e.g. DEPLOY_ENV=something npx underreact build) and this value will be made available in your client-side code on process.env.DEPLOY_ENV. For example, you may want to set it to 'staging' when building for a staging environment, 'production' when building for production, or 'test' when testing.
  • DEPLOY_ENV is not the same as NODE_ENV: see below.

A recommend way to use DEPLOY_ENV is set it in your npm scripts:

// package.json
{
  "scripts": {
    "build": "underreact run build", // if not set, DEPLOY_ENV will be set to `production` automatically
    "build:staging": "DEPLOY_ENV=staging underreact run build",
    "build:sandbox": "DEPLOY_ENV=sandbox underreact run build"
  }
}

Why set DEPLOY_ENV instead of NODE_ENV?

If you are used to using NODE_ENV to target different deployment environments, you should instead use DEPLOY_ENV, instead.

Underreact discourages setting NODE_ENV manually, as a number of libraries depend on its value and a wrong value could result in an unoptimized build. You should instead use Underreact's modes, which will set the right NODE_ENV for your app.

Configuration object properties

browserslist

Type: Array<string> | Object. A valid Browserslist value. Default:['>0.2%', 'not dead', 'not ie < 11', 'not op_mini all'].

This value is used by Autoprefixer to set vendor prefixes in the CSS of your stylesheets, and is used to determine Babel compilation via babel-preset-env.

You can also target different settings for different Underreact modes by sending an object:

// underreact.config.js
module.exports = {
  browserslist: {
    production: ['> 1%', 'ie 10'],
    development: ['last 1 chrome version', 'last 1 firefox version']
  }
};

compileNodeModules

Type: boolean | Array<string>. Default: true.

Many npm packages are now written in ES2015+ syntax, which is not compatible with all the browsers you may be supporting. So by default Underreact compiles all node_modules to ES5.

You can set compileNodeModules: false to disable compilation of node_modules, or pass an array of package names to selectively compile. In the example below we are only compiling the specified npm packages:

// underreact.config.js
module.exports = {
  compileNodeModules: ['p-finally', 'p-queue']
};

devServerHistoryFallback

Type: boolean. Default: false.

Set to true if you want to use HTML5 History for client-side routing (as opposed to hash routing). This configures the development server to fall back to index.html when you request nested routes.

Tip: This should only be intentionally turned on, when you know you're going to configure your server to allow for HTML5-History-powered client-side routing.

environmentVariables

Type: { [string]: string | number | boolean }.

Environment variables that you'd like to make available in your client-side bundle on process.env. For example, if you set environmentVariables: { ORIGIN: 'foo.com' }, you can use process.env.ORIGIN in your JavaScript.

hot

Type: boolean. Default: true.

Enable hot module reloading of Underreact. Read "How do I enable hot module reloading?" for more details.

htmlSource

Type: string|Promise<string>|Function<string | Promise<string>>. Default:see the default HTML.

The HTML template for your app, or a Promise that resolves to it. Read "Defining your HTML" for more details.

jsEntry

Type: string. Absolute path. Default: ${project-root}/src/index.js.

The entry JS file for your app. In a typical React app, this is the file where you'll use react-dom to render your app on an element.

In the default value, project-root refers to the directory of your underreact.config.js file.

liveReload

Type: boolean. Default: true.

Set it to false to prevent automatic reloading of your app on code changes. Switching off liveReload also disables hot reloading.

outputDirectory

Type string. Absolute path, please. Default: ${project-root}/_site/.

The directory where webesite files should be written.

You'll want to ignore this directory with .gitignore, .eslintignore, etc.

In the default value, project-root refers to the directory of your underreact.config.js file.

polyfill

Type: boolean. Default: true.

Whether or not to use Underreact's default polyfills. Read more at "Polyfilling newer JavaScript features".

port

Type: number. Default: 8080.

Preferred port for development servers. If the specified port is unavailable, another port is used.

postcssPlugins

Type: Array<Function>. Default: [].

All of the CSS that you import is run through PostCSS, so you can apply any PostCSS plugins to it. Underreact always runs Autoprefixer for you.

publicAssetsPath

Type: string. Default: underreact-assets.

The directory where Underreact assets will be placed, relative to the website's root.

By default, for example, the main JS chunk will be written to underreact-assets/js/main.chunk.js.

Tip: It's important to know about this value so you can set up caching and other asset configuration on your server.

publicDirectory

Type string. Absolute path, please. Default: ${project-root}/public/.

Any files you put into this directory will be copied, without processing, into the outputDirectory. You can put images, favicons, data files, and anything else you want in here. To reference these assets in your Javascript code, you can use the BASE_PATH environment variable. Read "How do I include SVGs, images, and videos?".

In the default value, project-root refers to the directory of your underreact.config.js file.

siteBasePath

Type: string. Default: '/'.

Path to the base directory on the domain where the site will be deployed. The default value is the domain's root. To help create valid links, Underreact exposes this value to your source code with an environment variable BASE_PATH. The table below gives an example of how Underreact sets BASE_PATH environment variable for a given siteBasePath value:

siteBasePath process.env.BASE_PATH
(not set) ""
"/" ""
"ketchup" "/ketchup"
"/ketchup" "/ketchup"
"/ketchup/" "/ketchup"

This normalization behaviour comes in handy when writing statements like process.env.BASE_PATH + '/my-path'. Read "How do I include SVGs, images, and videos?".

Underreact also passes this as a named parameter to the htmlSource function. Read "Defining your HTML" for more details.

Tip: There's a good chance your app isn't at the root of your domain. So this option represents the path of your site within that domain. For example, if your app is at https://www.special.com/ketchup/*, you should set siteBasePath: '/ketchup'.

stats

Type: string. Absolute path. Default: ``.

The directory where Webpack would write stats. By default, no stats file will be generated.

vendorModules

Type: Array<string>. Default: [].

Identifiers of npm packages that you want to be added to the vendor bundle. The purpose of the vendor bundle is to deliberately group dependencies that change relatively infrequently — so the vendor bundle can stay cached for longer than the others.

By default, the vendor bundle includes react and react-dom.

Tip: It is good idea to include big stable libraries your project depends on: for example, redux, moment.js, lodash, etc.

webpackConfigTransform

Type: config => transformedConfig. Default x => x (identify function).

If you want to make changes to the Webpack configuration beyond what's available in the above options, you can use this, the nuclear option. Your function receives the Webpack configuration that Underreact generates and returns a new Webpack configuration, representing your heart's desires.

Tip: You should think twice before using webpackConfigTransform, as Underreact tries its best to abstract away Webpack so that you can focus on your application.

webpackLoaders

Type: Array<Rule>.

Webpack Rules specifying additional loaders that you'd like to add to your Webpack configuration.

If you need more fine-grained control over the Webpack configuration, use webpackConfigTransform.

Tip: You should be careful before adding support for a new source type (for example, scss, less, ts), as it will make your application dependent on Webpack and its ecosystem.

webpackPlugins

Type: Array<Object>.

Additional plugins to add to your Webpack configuration.

For plugins exposed on the webpack module itself (e.g. webpack.DefinePlugin), you should use Underreact's version of Webpack instead of installing your own. That will prevent any version incompatibilities. That version is available in the context object passed to your configuration module function.

Here, for example, is how you could use the DefinePlugin in your underreact.config.js:

// underreact.config.js
module.exports = ({ webpack }) => {
  return {
    webpackPlugins: [new webpack.DefinePlugin(..)]
  };
}

FAQs

How do I make Jest use Underreact's Babel configuration ?

Jest expects a babel.config.js at the root of your application. Read "Exposing babel.config.js". Underreact will only work with Jest version >=23.6. To install Jest, follow the steps mentioned for Babel 7 in the official installation docs.

How do I dynamically import JavaScript modules or React components?

You can use the import() syntax to asynchronously load a valid JavaScript module. For example:

// src/index.js
import("./math").then(math => {
  console.log(math.add(16, 26)); // 42
});
// src/math.js
export default add(a,b) {
  return a + b;
}

Read official React docs for more information on how to load your React component dynamically.

How do I reduce my build size?

To reduce the build size you can try the following:

  • Avoid using custom polyfilling. Polyfilling is expensive, and using the default polyfill settings could save you a ton of bytes.
  • Use dynamic imports. Read more here
  • Target modern browsers with browserslist.
  • Selectively compile node_modules. By either selectively compiling or disabling compilation of node_modules, you can save some compilation time and reduce build size.

How do I include SVGs, images, and videos?

  • SVG Underreact uses @mapbox/svg-react-transformer to transform any imported SVG into a React component.
  • Images/Videos/Other Files: Underreact allows you to import any file type with the help of file-loader. In the example below we are going to import an image:
import logo from './logo.png';
console.log(logo); // /logo.84287d09.png

function Header() {
  // Import result is the URL of your image
  return <img src={logo} alt="Logo" />;
}

It is generally a good idea to use the above method for importing assets because:

  • It will throw a compilation error when an asset is missing.
  • It adds a hash to the filenames for client level cache invalidation.

If you cannot use this method, you can place assets in the publicDirectory and create a link using the BASE_PATH environment variable as shown below:

function Header() {
  return <img src={process.env.BASE_PATH + '/logo.png'} alt="Logo" />;
}

The BASE_PATH environment variable is automatically set by Underreact and is equivalent to the value of siteBasePath. BASE_PATH's value will never end with a /, even if your siteBasePath does.

How do I enable hot module reloading ?

Hot module reloading allows you to reload only the module that has changed, without affecting the rest of the code or reloading the page in the browser. This is different from liveReload which reloads the entire application when code changes. Underreact first tries to hot reload, then falls back to live reloading.

Underreact supports CSS and JavaScript hot reloading. CSS hot reloading should work out of the box. To implement hot reloading for JavaScript modules, you can follow the steps in the Webpack docs. (You can skip the parts about Webpack configuration, as it has already been taken care of by Underreact.)

For React apps, you'll' benefit from hot module reloading of React components. Luckily this setup is fairly straightforward. First, you need to get your own babel.config.js file by following the steps in "Exposing babel.config.js". Then, you need to install react-hot-loader:

npm install react-hot-loader

And then add it to your babel.config.js:

// babel.config.js
module.exports = {
  presets: ['@mapbox/babel-preset-mapbox'],
  plugins: ['react-hot-loader/babel']
};

You can then make any of your React components hot:

// src/app.js
import React from 'react';
import { hot } from 'react-hot-loader';

const App = () => <div>Hello World!</div>;

export default hot(module)(App);

You can read more about hot reloading your React components by reading react-hot-loader docs.

underreact's People

Contributors

danmactough avatar davidtheclark avatar elifitch avatar kepta avatar mbullington avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

underreact's Issues

Underreact Design

TLDR; This post discusses the design decisions of Underreact

What is Underreact?

Underreact will be a simple tool to build SPA application across Mapbox. The major goals of this project would be:

  • Simple and performant
  • Fits our need for small and large applications
  • Is configurable but also works without configuration

Why Underreact and not CRA?

I think it boils down to - SPA applications are too important for us. CRA tries to do a lot of things and not everything it does fits our use case (for eg. SCASS). It also doesn't let one customize the application and ejecting is pretty intimidating to think of. CRA is a great tool and a lot of the design decisions taken here are inspired by it. So if you love CRA, you would love Underreact too.

Stack details

HTML

HTML is pretty important, even for single page applications. Underreact will let users provide a function which would be expected to return an HTML string. This is similar to the HTML templating model found in Webpack, but a bit more powerful. The expected usage would be to set up a root element for frameworks like React and add any scripts/links if needed.

// undereact.config.js
{
  htmlSource: ({ renderJsBundles, renderCssLinks }) => `
      <html>
      <head>
       <title>Words that rhyme with fish</title>
        <link href="https://api.mapbox.com/mapbox-assembly/v0.21.2/assembly.min.css" rel="stylesheet">
        ${renderCssLinks()}
      </head>
      <body>
        <div id="app">
          <!-- React app will be rendered into this div -->
        </div>
        ${renderJsBundles()}
      </body>
  
      </html>
    `;
}

CSS

We would not allow the user to import CSS from within javascript which means no import './index.css'. We believe importing CSS is wrong since the import doesn't really give you anything tangible that can be used in the code.

To allow the user to add stylesheets to their project, they can simply add paths to stylesheets in the configuration file. These files would then be processed and concatanated by PostCSS. By default Underreact would be using Autoprefixer and CSSO PostCSS plugins, but the user can also supply their own list of PostCSS plugins.

Instead of using webpack-dev-server, we have decided to go with live-server which is much simpler and provides hot reloading of CSS.

Webpack

Webpack is currently the most popular tools out there for packaging Javascript applications. We do find it overly complex but the ecosystem and the popularity are hard to ignore. Keeping Webpack configuration from spiraling, we have decided to keep CSS and HTML build steps independent from it. This makes Underreact less reliant on a single tool and will ameliorate future maintenance headaches.

Importing Assets

Importing of assets with the ES module syntax is quite prevalent these days and not supporting it could hurt the adoption of Underreact. Hence, it would be support importing of JSON and images.

Babel

Underreact would come with our own babel-preset-mapbox. This preset would serve as an abstraction to the common babel presets and plugins we use everywhere. It would also let the user override any of them.

To keep onboarding simple, the .babelrc file is not required and a default Babel configuration is used behind the scenes. However, if the user wishes to customize Babel they are free to create their own .babelrc, which would supposedly use the babel-preset-mapbox.

We found that customizing browserslist is pretty common and having a special option in babel-preset-mapbox for just this purpose would be super helpful. This option would also allow PostCSS to consume browerslist.

underreact.config.json

  • stylesheets: A array contains the path to stylesheets the user wants to include in the bundle.
  • vendorModules: Allows the user to keep the vendor bundle relatively stable.
  • devServerHistoryFallback: hash mode by default, can enable the HTML5 browser history mode.
  • postcssPlugins
  • webpackLoaders
  • webpackPlugins
  • webpackConfigTransform: A callback which can be used to mutate the webpack configuration.
  • publicDirectory: Always need some place to put files and just have them copied over.
  • siteBasePath: The hostname of the web application, defaults to /.
  • publicAssetsPath: The path to place all the assets in, defaults to /assets.

Other configurations

  • We would be reading environment variables from a .env file, read more here.
  • Underreact would allow tweaking babel with the help of babelrc
  • Best enjoyed with stickler

@mapbox/frontend-platform @mapbox/frontend happy to hear your thoughts :)

Sourcemaps don't work with simply renaming css later

(Related #36)

With the recent implementation of hashing, the source map url in the generated css file doesn't get updated. This happens because we are providing a different output path.
As this will make any source map binded to provided path, simply calculating the hash and writting to new output would not work.

const writeOutput = root => {
return postcss(allPlugins)
.process(root, {
from: undefined,
to: output,
map: {
inline: sourceMap === SOURCE_MAP_INLINE
}
})
.then(result => {
if (hash) {
const fileHash = revHash(result.css);
output = output.replace(/\.css$/, `-${fileHash}.css`);
}
const promises = [promisify(fs.writeFile)(output, result.css)];
if (sourceMap === SOURCE_MAP_FILE) {
promises.push(promisify(fs.writeFile)(`${output}.map`, result.map));
}
return Promise.all(promises).then(() => output);
});
};

Svg React transform and CSS loader

It seems like url(xyz.svg) in a CSS is triggering svg-react-transformer-loader to convert it into a JS code and causing problems. The output CSS looks something like this:
image

I am not sure what would be the best way to prevent handling of this type of SVG import with react-transformer-loader. @davidtheclark any thoughts ?

Support mapbox-gl 2.0

Out of the box, underreact does not support the latest version of mapbox-gl. Since underreact is often used by apps that use Mapbox, the two projects should be compatible. Read more about why mapbox-gl breaks here: mapbox/mapbox-gl-js#10173

The basic problem is related to transpiling gl js's worker code:

Babel correctly transpiles the inlined worker, however it relies on injecting a bunch of code into the global-scope which is a part of its runtime. Since the gl-js workers are inlined, build tools in the application (like studio) are not able to recognize this. This leaves the worker bundle with transpiled code, but missing global scope functions.

There are two possible fixes, documented here:

  1. Ignore transpiling GL JS
  2. Transpile GL JS, but use the external worker file.

I suggest option 1: It saves us bundle size and may also speed up time to first render (it's 31% lighter, saving 68kb). GL JS 2.0 does not support IE11 at all anymore, so transpiling for IE11 isn't serving any purpose. Option 1 is also what GL JS team recommends.

It looks like we could fix this by updating the exclude regex in getConfigForNodeModules:

exclude: /@babel(?:\/|\\{1,2})runtime/,

We could also consider supporting both options, but defaulting to 1.

@kepta would love your input. This issue has a little bit of urgency, as many frontend apps at Mapbox are going to be switching to 2.0 soon.

Issues with NPM Link

It seems like when you npm link a module into a project using underreact, it isn't consumed by babel. I was able to verify this by importing (via npm install) @mapbox/style-components into mapbox-core-style-components and having no problem, and then using the exact same code from the exact same module via npm link, and I see an error. The error I see is exports is undefined coming from the first line of my module object.defineProperty(exports, '__esModule', { value: true });

The kicker though, is regardless of whether I install or link the module, that line is always present. My hunch is that babel doesn't know how to look for modules that are outside the project directory

core-js warning

Hi, I get this warning:

WARNING: We noticed you're using the `useBuiltIns` option without declaring a core-js version. Currently, we assume version 2.x when no version is passed. Since this default version will likely change in future versions of Babel, we recommend explicitly setting the core-js version you are using via the `corejs` option.

You should also be sure that the version you pass to the `corejs` option matches the version specified in your `package.json`'s `dependencies` section. If it doesn't, you need to run one of the following commands:

  npm install --save core-js@2    npm install --save core-js@3
  yarn add core-js@2              yarn add core-js@3

I use node 8 with yarn, here is my babel.config.js:

module.exports = {
  presets: ['@mapbox/babel-preset-mapbox', '@babel/preset-flow']
}

Build fails, saying to update caniuse-lite and browserslist

When trying to run underreact start on one of my projects, the build step failed with the following error:

[17:48:47 underreact] Starting underreact in development mode. Wait ...
[17:48:47 underreact] Using an external Babel config babel.config.js
ℹ 「wds」: Project is running at http://0.0.0.0:8080/
ℹ 「wds」: webpack output is served from /admin
ℹ 「wds」: Content not from webpack is served from /opt/app
ℹ 「wds」: 404s will fallback to /admin
Browserslist: caniuse-lite is outdated. Please run next command `npm update caniuse-lite browserslist`
[17:48:48 underreact] ERROR: Compilation error.

./node_modules/@mapbox/underreact/polyfill/index.js
Module build failed (from ./node_modules/babel-loader/lib/index.js):
BrowserslistError: [BABEL] /opt/app/node_modules/@mapbox/underreact/polyfill/index.js: Unknown version 79 of chrome (While processing: "base$0$0")
    at Array.reduce (<anonymous>)
    at Array.reduce (<anonymous>)

As recommended in the error message, I ran npm update caniuse-lite browserslist, but no updates were installed.

I then ran npm install browserslist@latest caniuse-lite@latest, and afterwards running underreact start successfully built and started my project.

support usage with typescript

Right now underreact mostly works fine with typescript, but we could improve compatibility. At a minimum, the SVG loader needs to include .ts and .tsx file type support:

const svgLoader = {
test: /\.svg$/,
// Applies react transformation only to svgs imported
// from Javascript files
issuer: /\.(js|jsx)$/,
use: {
loader: '@mapbox/svg-react-transformer-loader',
options: {
template: 'fancy'
}
}
};

Suggested browserslist value for modern sites

I think it would be helpful to document a suggested browserslist value for "modern" sites — that is, sites where you can safely expect that all visitors will be using a recent version of an evergreen browser (e.g. internal sites).

One push to repository

Even though underreact would be relatively easy to get started with. A user interested in prototyping might have to do bunch of chores to get the full experience:

  • setup babelrc
  • setup jest
  • setup stickler
  • create package.json and other files
  • setup deploy hooks like gh-pages
  • a demo src folder with a worker react hello world component.

I was wondering about having an interactive CLI scaffolding tool, which asks you a bunch of questions and sets up the whole thing. This would make all of the stickler apps consistent and reduce the amount of work to get started with the real stuff.

@mapbox/frontend-platform thoughts ?

Maybe log IP address URL alongside localhost for dev server

screen shot 2018-11-09 at 1 42 11 pm

I've relied on an IP address URL, as well as the localhost URL, to open the site I'm developing from another device on the same network (e.g. a phone). It's true that I could get that information another way, but it's awfully convenient to have it printed right there for me.

Finishing a first draft

Making a checklist about what I know needs to be done:

  • Easy CSS pipeline — maybe (probably?) a separate tool: basically Batfish's stylesheets processor.
  • Config validation with Fusspot.
  • Quick draft documentation, enough to facilitate beta testing.
  • A few more examples using a few more options.
  • New package name (chunk-light is taken).
  • Add to HTML any <link>s for CSS assets created by Webpack plugins the user provides.
  • Env variable option.
  • Expose Webpack version so configs can use it to add plugins.
  • Reject HTML with closing head and body tags, which we need so we know where to put things.
  • Optionally write stats during build.

The default value of `DEPLOY_ENV` is confusing

The default value for DEPLOY_ENV is being set to production. This leads to a non-intuitive behaviour of seeing a build in my local machine dangerously using production API. I think a fix would be either to make setting DEPLOY_ENV during build explicit or using development or local as a default value. I personally think development would cause least surprises.

Thanks to @davidtheclark for pointing this one out.

Modern browser test

Underract uses Javascript expression 'fetch' in window && 'assign' in Object as its modern browser test to conditionally load polyfills (read more about this approach) .

We should clearly document what this means and also document all the minimum versions of major browser which will pass the default modern browser test. The idea here is to not penalise majority of the users by loading the polyfill.js.

One pitfall of this approach would be that if a user wants to polyfill something in modern browsers, they would either need to keep it in their main bundle or raise the bar with the help of modern browser test. I think this is a necessary tradeoff for conditionally loading polyfill.js.

No CSS source maps in development mode

In development mode, if I inspect an element to see why it's styled the way it is, I'm not seeing references to the original source in the devtools: just a reference to the <style> tag that Webpack put that CSS into.

screen shot 2018-11-09 at 1 01 23 pm

This could hamper development.

When I run build and serve-static, I do see nice source maps.

Ditch fs-extra

In light of memory leaking bug (thanks to @davidtheclark) in node-graceful-fs isaacs/node-graceful-fs#102 which fs-extra depends on, we should not be using fs-extra. Since this project is in initial phases, it would be relatively easy to port. Also, node 8+ have a native solution to the promisification of node API's.
Currently we have two options

  1. Continue using pify since it is already being used and works well.
  2. Port to using the util.promisify, which is a first party wrapper for node's callback API.

I am a bit inclined to no.2 because it is a standard API and would work well with existing node ecosystem. To support the node v6 we can use the polyfill util/promisify.
@davidtheclark thoughts?

Routing example is broken

When I try to run it I get this error:

[12:52:11 underreact] Starting underreact in development mode. Wait ...
[12:52:11 underreact] ERROR: Could not find the entry Javascript file /Users/davidclark/dev/mapbox/underreact/examples/routing/src/index.js.

Error: Could not find the entry Javascript file /Users/davidclark/dev/mapbox/underreact/examples/routing/src/index.js.
    at main (/Users/davidclark/dev/mapbox/underreact/commands/start.js:25:11)
    at config.then.urc (/Users/davidclark/dev/mapbox/underreact/bin/underreact.js:121:18)
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:189:7)
    at Function.Module.runMain (module.js:696:11)
    at startup (bootstrap_node.js:204:16)
    at bootstrap_node.js:625:3

Future of the project

Existential crisis time: what is the future of underreact?

When we created the project, the front end build ecosystem was a bit different; create react app was significantly less flexible than it is today for example. Knowledge of how underreact works is highly concentrated (bus factor), and it's uncertain whether investing in institutionalizing underreact knowledge is worth it.

In the meantime, it has been a blocker in a handful of cases, particularly #108.

So let's have this discussion. Should we investigate replacing underreact with a community-maintained solution? cc @mapbox/map-design-studio @mapbox/frontend @mapbox/console

Flow error in node_modules

I get this error when compile mapbox-gl-style-spec in node_modules:

Module build failed (from ./node_modules/babel-loader/lib/index.js):
SyntaxError: /Users/jingsam/projects/underreact-test/node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/interpolate.js: Unexpected token (10:12)

   8 | import { hcl, lab } from '../../util/color_spaces';
   9 | 
> 10 | import type { Stops } from '../stops';
     |             ^
  11 | import type { Expression } from '../expression';
  12 | import type ParsingContext from '../parsing_context';
  13 | import type EvaluationContext from '../evaluation_context';

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

here is my babel.config.js:

module.exports = {
  presets: ['@mapbox/babel-preset-mapbox', '@babel/preset-flow']
}

Images not loading using default publicDirectory

I've added some image files to the default publicDirectory path ({project-root}/src/public/), and am referencing them in the code at /{site-base-path}/foo.png. The images are not rendering locally. I've tried various combinations of referencing the image file like {site-base-path}/foo.png (without a prepended slash), changing the siteBasePath to include a prepended slash, etc, but nothing has worked.

Any help debugging would be greatly appreciated. Thanks!

Slack thread for context here

cc/ @kepta

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.