Some larger eco-systems like content management systems don't have the option or desire to render JSX (client or server side) due to the sheer number of libraries already in use. In these instances a export approach allows your project to still use React, but now pre-render and export JSX to larger eco-systems as needed.
Webpack JSX Exports takes all incoming JSX files (of a given plugin configuration), then at the end of a standard Webpack build the exporter will process these configured files and write them to disk.
The process for gathering JSX is done using a glob methods devoid of Webpack file gathering. This ensures that plugin configurations can still export files that might not be part of the overall Webpack build.
Furthermore, the exporting method uses a babel register approach to reduce the amount of AST parsing and traversing but also allow for export to work devoid of Webpack all together in a node script.
It's that simple!
npm i --save-dev webpack-jsx-export
yarn add --dev webpack-jsx-export
Import webpack-jsx-export
into your Webpack configuration file:
const WebpackJSXExport = require('webpack-jsx-export');
Instantiate new WebpackJSXExport(...)
class within Webpack's plugin configuration array:
{
"plugins": [
new WebpackJSXExport()
]
}
new WebpackJSXExport({
...options...
})
Option | Types | Description | Default |
---|---|---|---|
files |
Object Array | Defines both input and output paths of JSX and exported file(s) | [] |
files.input |
String | Input location of individual or glob .JSX file(s) | -- |
files.output |
String | Output location of exported JSX files | -- |
files.extension |
String or Function | Defines exported file(s) extension type | .html |
files.filter |
Function | Filters away imported .JSX files that you wish NOT to be exported | -- |
globals |
Object | Defines any global namespaces or libraries required to process your JSX | {} |
plugins |
Object | Defines custom plugins used during the processing of each exported JSX file | {} |
comment |
String, Boolean or Function | Defines a custom comment prepended to the top of exported files | -- |
warnings |
Boolean | Defines if JSX prop warnings should be shown in terminal or not | true |
With the files
option, you must specify both input
and output
for source JSX files and location of where exports will be written:
new WebpackJSXExport({
files: [{
input: './input/location/*.jsx',
output: './export/location/'
}]
})
Multiple locations for input, single export location:
new WebpackJSXExport({
files: [{
input: './input/location-one/*.jsx',
output: './export/location/'
}, {
input: './input/location-two/*.jsx',
output: './export/location/'
}]
})
If you only need to target a single JSX file for input you can do so:
new WebpackJSXExport({
files: [{
input: './input/location/specific.jsx',
output: './export/location/'
}]
})
By default the exported filename will be equal to the input JSX filename, however if you want to have a custom name for your exported file, you can specify it in the output path:
new WebpackJSXExport({
files: [{
input: './input/location/specific.jsx',
output: './export/location/custom-name.html'
}]
})
By default the exported file extension is .html
; however if you wish to change that, simply use the extension
option to define a custom one:
new WebpackJSXExport({
files: [{
input: './input/location-one/specific.jsx',
output: './export/location/custom-name',
extension: '.php'
}]
})
Or, if you want need different extension types across a glob input, use the extension option as a filtering method:
new WebpackJSXExport({
files: [{
input: './input/location-one/*.jsx',
output: './export/location/custom-name',
extension: (file) => {
if (file.name === 'Razor.jsx') { file.extension = '.cshtml' }
return file;
}
}]
})
Please note you must return file
to send changes off to export process.
Lastly files
options offers a filter
method that allows you to filter away .JSX files you wish NOT to be exported under a glob input scenario:
new WebpackJSXExport({
files: [{
input: './input/location-one/*.jsx',
output: './export/location/custom-name',
filter: (file) => {
if (file.name.indexOf('special-file')) {
return false;
}
return file;
}
}]
})
Please note, the returning of a false
denotes a particular file not to be exported; so a return file
is required.
The filter
option is also a way to re-define a file's source location before being shipped off to the export process. For instance, if the JSX file(s) in question have a schema up which defines the JSX somewhere other than root, we can re-target our file.source
to that location:
new WebpackJSXExport({
files: [{
input: './input/location-one/*.jsx',
output: './export/location/custom-name',
filter: (file) => {
if (file.source.default.schema) {
file.source.default = file.source.default.schema.special.place.source;
}
return file;
}
}]
})
The template
option merges processed files with HTML templates to be part of a larger export. This is useful if you want to create full documents out of a glob of JSX exports.
Note: While templates are defined in .html files, the configred files.extension
or files.output
path's used extension still persists as the file extension to be expected on exported files.
new WebpackJSXExport({
template: '/path/to/template.html',
files: [{
input: './input/location-one/*.jsx',
output: './export/location/',
}]
})
[comment]
<!DOCTYPE html>
<html lang="en-US" dir="ltr">
<head>
<title>[name]</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
[source]
</body>
</html>
The template's can use the following outof the box placeholders to replaced with data during export:
Placeholder | Description |
---|---|
[name] |
Input JSX file's name |
[source] |
Input JSX file's rendered source |
[comment] |
Configured comment for given export |
However you can also create/pass custom data placeholders to be processed as well:
new WebpackJSXExport({
template: {
file: '/path/to/template.html',
placeholders: (holder) => {
holder.slogan = 'Super cool page title slogan';
holder.styles = `page-${file.name}`;
holder.thing = 'Hello world! I am a THING!';
}
}
})
[comment]
<!DOCTYPE html>
<html lang="en-US" dir="ltr">
<head>
<title>[name] - [slogan]</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body class="[styles]">
[source]
<p>
[thing]
</p>
</body>
</html>
Lastly, you can (like filters) use the template.files
option as a function to utilize multiple templates based on one or another exporting JSX file:
new WebpackJSXExport({
template: {
file: (file, template) => {
if (file.name.indexOf('group-one') !== -1) {
return '/path/to/group-one-template.html';
}
if (file.name.indexOf('group-two') !== -1) {
return '/path/to/group-two-template.html';
}
return '/path/to/basic-template.html';
},
placeholders: (holder) => {
holder.slogan = 'Super cool page title slogan';
holder.styles = `page-${file.name}`;
holder.thing = 'Hello world! I am a THING!';
}
}
})
Please note, when using template.file
as a function a template path must be returned. If no template path is returned this process is skipped and the configured input JSX file(s) will be exported devoid of any templating.
There are two plugin types, input
and output
. The input
plugin types are plugins that support the consuming (pre-rendering) of your JSX files, while the output
are plugins that support exporting (post-rendering) of your JSX files.
new WebpackJSXExport({
files: [{
input: './input/location-one/*.jsx',
output: './export/location/'
}],
plugins: {
input: [],
output: []
}
})
The plugins.input
option allows you to specify additional plugins to support the processing of JSX syntax before being rendered for export. This is useful if your JSX uses a newer syntax that requires a babel plugin(s).
new WebpackJSXExport({
files: [{
input: './input/location-one/',
output: './export/location/'
}],
plugins: {
input: [
'@babel/implicit-function'
]
}
})
This option allows you to specify additional plugins to process the exported JSX static rendering(s). This is useful if your JSX uses server-side syntax like Adobe HTL, .Net razor or PHP that needs to be custom processed before written to disk.
new WebpackJSXExport({
files: [{
input: './input/location-one/',
output: './export/location/'
}],
plugins: {
output: [
HTLPlugin(),
RazorPlugin(),
PHPPlugin()
]
}
})
Please note there is currently no large community behind export plugins, so each plugin (if not found in /plugins/
) you will need to be crafted yourself for your project's exporting needs.
For more information on plugin crafting and available API see /plugins/README.md
.
Because WebpackJSXExport approaches JSX babel transpile with a plugin register approach, context of global namespaces or libraries is foreign to the export process. The files.globals
option allows you to define these global parts required to successfully render a standalone version of your JSX file(s).
new WebpackJSXExport({
files: [{
input: './input/location-one/*.jsx',
output: './export/location/custom-name'
}],
globals: {
'Utilities': path.resolve('../../utilities.jsx'),
'Helpers': path.resolve('../../helpers.jsx')
}
})
In the above example, its assumed our input JSX file(s) are using a Utilities
and Helpers
global namespace for two JS libraries in some way defined elsewhere. We define these global namespaces here so our export process has context when faced with JSX file that might be using them.
At the top of each exported file, a comment is included to denote to developers (at a later point) that these file(s) were auto generated.
You can supply your own comment here using the comment
option.
new WebpackJSXExport({
comment: 'Please do not edit this file! This was generated at build!'
})
or, if you would like to have custom comments based on different files being exported, you can supply a function to the comment
option:
new WebpackJSXExport({
comment: (file) => {
if (file.name.indexOf('something') !== -1) {
file.comment = 'Custom comment for "something" files';
}
if (file.name.indexOf('other') !== -1) {
file.comment = 'Custom comment for "other" files';
}
return file;
}
})
Lastly, if you wish to have no comment (not recommended), simply supply a false
value:
new WebpackJSXExport({
comment: false
})
If you for some reason would like to obscure any JSX warnings you noramlly see in browser console, from the terminal during exporting; set the warnings
option to false
.
new WebpackJSXExport({
warnings: false
})
By default the warnings
option is true
with the idea being its better hoist "need to fix issue" up higher and sooner for developers to see, rather than out in production in browser console.
While its recommended that you use WebpackJSXExport in a actual webpack build configuration, it can also be ran from a node script due to how babel plugins are registered prior to rendering.
Here is a basic node script that uses WebpackJSXExport:
const WebpackJSXExport = require('webpack-jsx-export');
const exporter = new WebpackJSXExport({
files: [{
input: './input/location/*.jsx',
output: './export/location/'
}]
});
exporter.run();
Note we have a .run()
method to actual perform the exporting. This gives finer control between when instantiating a exporter, its configuration and when the exporting runs.
Webpack JSX Export uses babel plugin register approach to transpile JSX source (and syntax sugar) into markup across both Webpack builds, or NodeJS scripts. The baseline babel transpile plugins used by WebpackJSXExport are the following:
Plugin | Description | URL |
---|---|---|
babel-plugin-file-loader |
File loader | Plugin Details |
babel-plugin-transform-require-context |
Require importing | Plugin Details |
@babel/plugin-transform-react-jsx |
JSX Transpile | Plugin Details |
@babel/plugin-proposal-object-rest-spread |
Object Spread | Plugin Details |
@babel/plugin-proposal-class-properties |
JS Classes | Plugin Details |
@babel/plugin-transform-react-display-name |
React Helper | Plugin Details |
@babel/plugin-proposal-nullish-coalescing-operator |
JS Syntax Feature | Plugin Details |
@babel/plugin-proposal-async-generator-functions |
JS Syntax Feature | Plugin Details |
@babel/plugin-transform-for-of |
JS Syntax Feature | Plugin Details |
@babel/plugin-proposal-optional-chaining |
JS Syntax Feature | Plugin Details |
@babel/plugin-transform-reserved-words |
JS Syntax Feature | Plugin Details |
Webpack JSX Export while run under a Webpack configuration will automatically carry over any alias pathing you may have configured for you build. You wont need to maintain this yourself in the plugins option.
require.resolve('babel-plugin-module-resolver'), { // (see: https://www.npmjs.com/package/babel-plugin-module-resolver)
'alias': (compiler) ? compiler.options.resovle : {}
}
Furthermore two very common global namespaces for React
and PropType
have been setup for you, so again you don't need to maintain these in the plugin's options.
require.resolve('babel-plugin-import-globals'), { // (see: https://www.npmjs.com/package/babel-plugin-import-globals)
"React": "react",
"PropTypes": 'prop-types'
}
While both of the above come out of the box, take note on how they are being used as you can pass your own versions through the plugin's options if you say, want to add more required global namespaces or context resolver(s).
Webpack JSX Export plugin also comes with two built-in tags for giving you finer control over corner cases where a particular part of code should be there in say a Webpack Dev instance, but not there during final production export.
<export>
Anything in me will be written unwrapped from the <export> tag and written to disk.
</export>
<no-export>
Anything in me is removed from the export and will not be written to disk.
</no-export>
Webpack JSX Export comes with a number of tests found under /tests
.
These are here to help you better understand the expectations of each option we covered above.
Simply run npm run test
or yarn test
from the root of the plugin to run all tests. Running a test will produce a /dist/[test]
directories.
If you would like to change a test, update the root package.json file's test
script to use any of the /test/*.test.config.js
files.