Skip to content

Instantly share code, notes, and snippets.

@tauren
Last active March 14, 2017 20:35
Show Gist options
  • Save tauren/5aa2483b63c703ecff199e4a927a7329 to your computer and use it in GitHub Desktop.
Save tauren/5aa2483b63c703ecff199e4a927a7329 to your computer and use it in GitHub Desktop.
Neutrino customization script WIP
'use strict';
// TODO: Upgraded to neutrino@beta (v5). Make sure to upgrade to final release version
// which should be available mid March. Here are some links on what has changed and
// how to update old config to new:
// https://github.com/mozilla-neutrino/neutrino-dev/blob/v5.0.0-beta/docs/upgrading-neutrino.md
// https://github.com/mozilla-neutrino/neutrino-dev/pull/86#issue-211140309
const merge = require('deepmerge');
const webpack = require('webpack')
const cssnext = require('cssnext')
const postcssReporter = require('postcss-reporter')
const CSS_LOADER = require.resolve('css-loader');
const STYLE_LOADER = require.resolve('style-loader');
const POSTCSS_LOADER = require.resolve('postcss-loader');
const HTML_LOADER = require.resolve('html-loader');
const RAW_LOADER = require.resolve('raw-loader');
const BABEL_LOADER = require.resolve('babel-loader');
const REACT_MARKDOWN_LOADER = require.resolve('react-markdown-loader');
const componentTemplates = require('./src/templates')
const path = require('path')
const PATCH_LOADER = require.resolve('./patchReactWebpackLoader');
const SVGSYMBOL_LOADER = require.resolve('./svgSymbolLoader');
module.exports = neutrino => {
// ------ Patch ReactDOM to support named <slot> ------ //
neutrino.config.module
.rule('patch')
.test(/HTMLDOMPropertyConfig\.js$/)
.use('patch')
.loader(PATCH_LOADER)
// TODO: Do this only for dev environment, not production
// Remove minify plugin
neutrino.config.plugins.delete('minify');
// ------ Set publicPath so scripts load on all routes ------ //
neutrino.config.output.publicPath('/')
// ------ SVG Loading ------ //
neutrino.config.module.rules.delete('svg')
// Process svg files in src/elements/Icons with custom loader
// TODO: Process all other svg files normally
neutrino.config.module
.rule('svg')
.test(/\.svg$/)
.include
.add(path.resolve(__dirname, 'src/elements/Icons'))
.end()
.use('babel')
.loader(BABEL_LOADER)
.options(neutrino.config.module.rule('compile').use('babel').get('options'))
.tap(options => merge(options, {
plugins: [
["transform-react-jsx", { pragma: "h" }]
]
}))
.end()
.use('svgsymbol').loader(SVGSYMBOL_LOADER).end()
// ------ CSS Loading, preprocessing, and injection ------ //
// Remove rule defined in preset since we will be completely replacing them
neutrino.config.module.rules.delete('style')
// Process global css files, such as those in src/themes and node_modules
neutrino.config.module
.rule('style')
.test(/\.css$/)
.exclude
.add(path.resolve(__dirname, 'src/elements'))
.add(path.resolve(__dirname, 'src/app'))
.end()
// Inject string of styles into <style> tag
.use('style').loader(STYLE_LOADER).end()
// Transform @import, css modules, etc into a string
.use('css').loader(CSS_LOADER).end()
// Transform loaded css with postcss plugins
.use('postcss').loader(POSTCSS_LOADER).end()
// Process all css in src/app as CSS Modules
neutrino.config.module
.rule('style-modules')
.test(/\.css$/)
.include
.add(path.resolve(__dirname, 'src/app'))
.end()
// Inject string of styles into <style> tag
.use('style').loader(STYLE_LOADER).end()
// Transform @import, css modules, etc into a string
.use('css')
.loader('css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]')
// NOTE: This should work, but results in `css-loader?{modules:true}`. Probably a css-loader bug.
// .loader(CSS_LOADER).options({ modules: true, importLoaders: 1 })
.end()
// Transform loaded css with postcss plugins
.use('postcss')
.loader(POSTCSS_LOADER)
// Process all css in src/elements into a raw string to be used within shadow DOM in web components
neutrino.config.module
.rule('style-elements')
.test(/\.css$/)
.include
.add(path.resolve(__dirname, 'src/elements'))
.end()
// Transform @import, css modules, etc into a string
.use('css')
.loader(CSS_LOADER)
.end()
// Transform loaded css with postcss plugins
.use('postcss')
.loader(POSTCSS_LOADER)
// ------ Update Babel Options ------ //
// Add support for class properties
neutrino.config.module
.rule('compile')
.use('babel')
.tap(options => merge(options, {
plugins: [
'babel-plugin-transform-class-properties'
]
}))
// Set JSX pragma to use `h` instead of `React.createElement`
// This is to support JSX syntax via hyperscript in custom elements built with SkateJS
// Alternatively, /** @jsx h */ could be added to the top of each custom element source
neutrino.config.module
.rule('compile-elements')
.test(/\.jsx?$/)
.include
.add(path.resolve(__dirname, 'src/elements'))
.end()
.use('babel')
.loader(BABEL_LOADER)
.options(neutrino.config.module.rule('compile').use('babel').get('options'))
.tap(options => merge(options, {
plugins: [
["transform-react-jsx", { pragma: "h" }]
]
}))
// ------ Markdown Loading ------ //
// Make sure markdown files get processed with the compile rule
neutrino.config.module.rule('compile').test(/(\.jsx?|\.md)$/)
// Convert markdown file into react component tree
neutrino.config.module
.rule('markdown')
.test(/\.md$/)
.use('markdown')
.loader(REACT_MARKDOWN_LOADER)
.options({
templates: componentTemplates.component
})
// ------ HOW TO ------ //
// Remove minify plugin
// neutrino.config.plugins.delete('minify');
// Use config from one node as the config for another
// .options(neutrino.config.module.rule('compile').use('babel').get('options'))
// Example showing how debug by outputing the config of a chain
// console.log(neutrino.config.module.rule('css-components').toConfig())
// console.log(neutrino.config.toConfig())
};
/**
* Add support to ReactDOM v15 for slot attribute
*
* This makes React allow the `slot` attribute on native DOM elements,
* enabling the use of named <slot> tags in custom elements
*/
module.exports = function patchReactWebpackLoader(source) {
return source.replace(/Properties: {/, 'Properties: {\n slot:0,')
}
/**
* Convert svg file with symbols into a hash map in jsx format
*/
module.exports = function svgSymbolLoader(source) {
const regex = /<title>([^<]*)<\/title>\n(<path[^<]*<\/path>)/g
let matches
let out = `import { h } from 'skatejs'\n
export default {\n`
while (matches = regex.exec( source )) {
out += `'${matches[1]}': ${matches[2]},\n`
}
out += '}\n'
return out
}
@tauren
Copy link
Author

tauren commented Mar 14, 2017

Description

This neutrino customization is for a project that contains a collection of Custom Elements. The project also contains a react application for users to explore and read the documentation for each custom element. The elements are exported without dependencies on React, etc.

What does it do?

Patch ReactDOM

ReactDOM doesn't support <slot> tags which are needed to use Custom Elements (Web Components). The following patches the HTMLDOMPropertyConfig.js file in the ReactDOM package to add support.

  neutrino.config.module
    .rule('patch')
    .test(/HTMLDOMPropertyConfig\.js$/)
    .use('patch')
    .loader(PATCH_LOADER)

Splits SVG file into javascript object

Splits an SVG file containing multiple symbols into a hash map of jsx markup. For instance, import icons from './icons.svg' will result in icons having a value like this:

{
  "accessibility": <path d="M12 2c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm9 7h-6v13h-2v-6h-2v6H9V9H3V7h18v2z"/>,
  "add": <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>,
}

First, it deletes the existing svg rule. Objective: do not delete existing rule, only override it for the Icons directory

Then it creates a new svg rule to process any svg files in the Icons element using a custom loader. This uses the same babel config that is used later in the script. Objective: make this more DRY, do not repeat the babel config

  // Process svg files in src/elements/Icons with custom loader
  // TODO: Process all other svg files normally
  neutrino.config.module
    .rule('svg')
    .test(/\.svg$/)
    .include
      .add(path.resolve(__dirname, 'src/elements/Icons'))
      .end()
    .use('babel')
      .loader(BABEL_LOADER)
        .options(neutrino.config.module.rule('compile').use('babel').get('options'))
        .tap(options => merge(options, {
          plugins: [
            ["transform-react-jsx", { pragma: "h" }]
          ]
        }))
      .end()
    .use('svgsymbol').loader(SVGSYMBOL_LOADER).end()

Processes CSS in various ways

The CSS loading needs to be handled differently for various parts of this project. This is because the project contains both pure custom elements as well as react components.

  • The default style rule is replaced with a rule that injects global css, such as those in src/themes and node_modules
  • The style-modules rule loads styles for the app's React components as CSS modules
  • The style-elements rule loads css for custom elements into a raw string to be included within element shadow DOM

Update Babel Options

  • Adds support for class properties
  • Uses h instead of React.createElement only for the custom elements

Objective: make this more DRY, do not repeat the babel config

  // Set JSX pragma to use `h` instead of `React.createElement`
  // This is to support JSX syntax via hyperscript in custom elements built with SkateJS
  // Alternatively, /** @jsx h */ could be added to the top of each custom element source
  neutrino.config.module
    .rule('compile-elements')
    .test(/\.jsx?$/)
    .include
      .add(path.resolve(__dirname, 'src/elements'))
      .end()
    .use('babel')
      .loader(BABEL_LOADER)
        .options(neutrino.config.module.rule('compile').use('babel').get('options'))
        .tap(options => merge(options, {
          plugins: [
            ["transform-react-jsx", { pragma: "h" }]
          ]
        }))

Compile Markdown files

Markdown files are included with each custom element and are compiled into react components to be displayed as documentation pages.

  • Update the compile rule to compile both jsx? and md files.
  • Process md files using the markdown loader
  // Make sure markdown files get processed with the compile rule 
  neutrino.config.module.rule('compile').test(/(\.jsx?|\.md)$/)

  // Convert markdown file into react component tree
  neutrino.config.module
    .rule('markdown')
    .test(/\.md$/)
    .use('markdown')      
      .loader(REACT_MARKDOWN_LOADER)
      .options({ 
        templates: componentTemplates.component
      })

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment