Awesome
<div align="center"> <img src="logo.png" alt="PWA" height="200" /> </div> <div align="center"> <a href="https://npmjs.org/package/@pwa/cli"> <img src="https://badgen.now.sh/npm/v/@pwa/cli" alt="version" /> </a> <a href="https://travis-ci.org/lukeed/pwa"> <img src="https://badgen.now.sh/travis/lukeed/pwa" alt="travis" /> </a> <a href="https://npmjs.org/package/@pwa/cli"> <img src="https://badgen.now.sh/npm/dm/@pwa/cli" alt="downloads" /> </a> </div><p align="center"><strong>WORK IN PROGRESS</strong></p>
Features
-
Framework Agnostic<br> Build with your preferred framework or with none at all!<br>Official presets for Preact, React, Vue, and Svelte.
-
Plug 'n Play<br> Don't worry about configuration, unless you want to.<br>Presets and plugins are automatically applied. Just install and go!
-
Fully Extensible<br> Includes a plugin system that allows for easy, fine-grain control of your configuration... when needed.
-
Feature Rich<br> Supports Babel, Bublé, Browserslist, TypeScript, PostCSS, ESLint, Prettier, and Service Workers out of the box!
-
Instant Prototyping<br> Quickly scaffold new projects with your preferred view library and toolkit.<br>Kick it off with a perfect Lighthouse score!
-
Static Site Generator<br> Export your routes as "pre-rendered" HTML.<br>Great for SEO and works on any static hosting service.
Installation
PWA is split up into two main components (core
and cli
) in addition to its list of presets and plugins.
While most will opt for the CLI, the
core
module handles all configuration and can be used as a standalone module.
Please refer to each package for installation, API, and Usage information.
Quick Start
# Install globally
$ npm install --global @pwa/cli
# OR
$ yarn global add @pwa/cli
# Display CLI's help text
$ pwa --help
# Generate new project
$ pwa init
Note: The
global
modifiers are only required for global command-line usage!<br> LocaldevDependency
installation will also work, but thenpwa
usage is limited to the project.
Concepts
Please read about Progressive Web Apps if the term is unfamiliar to you.
Presets
Presets are collections of plugins that are tailored for a particular framework.
While there may be "official" presets, this does not mean that PWA can only support these candidates! The current options are:
These packages are auto-loaded during PWA's initialization and are applied first, before any Plugins or custom configuration. This means that you always have the option to override a value or setting shipped within the Preset.
Plugins
Plugins are (typically) individual features or chunks of configuration that are encapsulated for easy/automatic application within your build process.
While there may be "official" plugins, this does not mean that PWA can only support these functionalities! The current plugins include:
@pwa/plugin-buble
@pwa/plugin-brotli
@pwa/plugin-critters
@pwa/plugin-eslint
@pwa/plugin-gzip
@pwa/plugin-imagemin
@pwa/plugin-offline
@pwa/plugin-prettier
@pwa/plugin-sw-precache
@pwa/plugin-sw-workbox
@pwa/plugin-typescript
@pwa/plugin-zopfli
These packages are auto-loaded during PWA's initialization and are applied second, after any Presets and before custom configuration. This allows Plugins to override settings from Presets.
Plugins may (sometimes) expose a new key on the config tree and then reference this value later in composition. This allows the end-user to change the Plugin's settings before running the build.
Please see
@pwa/plugin-critters
for an example of this practice.
Commands
This section applies to
@pwa/cli
specifically.
Build
Build your application for production
$ pwa build --help
Description
Build production assets
Usage
$ pwa build [src] [options]
Options
--analyze Launch interactive Analyzer to inspect production bundle(s)
-o, --dest Path to output directory (default build)
-h, --help Displays this message
Export
Export routes' HTML for static hosting
Instead of --routes
, you may define a routes
array within pwa.config.js
config file.
If no routes are defined in either location, PWA will traverse your "@pages"
-aliased directory (default: src/pages/**
) and attempt to infer URL patterns from the file structure.
In the event that no files exist within that directory, PWA will show a warning but still scrape the index ("/"
) route.
$ pwa export --help
Description
Export pre-rendered pages
Usage
$ pwa export [src] [options]
Options
-o, --dest Path to output directory (default build)
-w, --wait Time (ms) to wait before scraping each route (default 0)
-r, --routes Comma-delimited list of routes to export
-i, --insecure Launch Chrome Headless without sandbox
-h, --help Displays this message
Important: Using
export
requires a local version of Chrome installed! Seechrome-launcher
.<br>Additionally, the--insecure
flag launches Chrome without sandboxing. See here and here for help.
Watch
Develop within a live-reload server
Within your pwa.config.js
's webpack
config, any/all devServer
options are passed to Webpack Dev Server.
$ pwa watch --help
Description
Start development server
Usage
$ pwa watch [src] [options]
Options
-H, --host A hostname on which to start the application (default localhost)
-p, --port A port number on which to start the application (default 8080)
-q, --quiet Disable logging to terminal, including errors and warnings
--https Run the application over HTTP/2 with HTTPS
--key Path to custom SSL certificate key
--cert Path to custom SSL certificate
--cacert Path to custom CA certificate override
-h, --help Displays this message
Build vs Export
Export can be thought of as "Build 2.0" — it spins up a Headless Chrome browser and programmatically scrapes your routes.
This is ideal for SEO, PWA behavior, and all-around performance purposes, as your content will exist on the page before the JavaScript application is downloaded, parsed, boots, and (finally) renders the content.
The generated HTML pages will be placed in your build
directory. A /login
route will be exported as build/login/index.html
— this makes it compatible with even the "dumbest" of static hosting services!
Note: Running
export
will automatically runbuild
before scraping.
Configuration
Overview
All configuration within the PWA tree is mutable! Presets, Plugins, and your custom config file write into the same object(s). This is great for composability and extensibility, but be warned that your custom config may break the build if you're not careful.
:bulb: Official presets & plugins are controlled releases and are ensured to play nicely with one another.
The config object(s) for your project are assembled in this sequence:
- Presets: All non-
webpack
config keys - Plugins: All non-
webpack
config keys - Custom: All non-
webpack
config keys - Presets: The
webpack
config key, if any - Plugins: The
webpack
config key, if any - Custom: The
webpack
config key, if any
Because the final config object is passed to Webpack, internally, the webpack
key always runs last as it composes & moves everything into its relevant loaders, plugins, etc.
Important: When defining a custom
webpack
key it must always be a function!
Mutations
Every config key can be defined or mutated in the same way!
Any non-Function
key will overwrite the existing value. This allows strong opinions and/or allows a Plugin to define a new config key and reference it later on.
Any Function
key will receive the existing, matching config-value for direct mutation. This is for fine-grain control over the existing config.
// defaults:
exports.hello = { foo:1, bar:2 };
exports.world = ['How', 'are', 'you?'];
// preset/plugin/custom:
exports.hello = function (config) {
config.bar = 42;
config.baz = [7, 8, 9];
}
exports.world = ['I', 'am', 'fine'];
exports.HOWDY = 'PARTNER!';
// result:
exports.hello = {
foo: 1,
bar: 42,
baz: [7, 8, 9]
}
exports.world = ['I', 'am', 'fine'];
exports.HOWDY = 'PARTNER!';
Functions
Any config key that is a function will have the signature of (config, env, opts)
.
config
Type: Mixed
This will be the existing value for the current key. It will typically be an Object, but not always.
It will also be undefined
if/when defining a new config key — if you know that to be the case, you shouldn't be using a Function~!
env
Type: Object
Will be the environmental values for this command.<br>
This is passed from @pwa/core
's options.
The env.cwd
, env.src
, env.dest
, env.log
, env.production
and env.webpack
keys are always defined.<br>Anything else is contextual information for the current command being run.
opts
Type: Object
Direct access to configuraton keys, except webpack
.
As an example, this can be used within a Plugin for gaining insight or gaining access to other packages' settings.
The default config keys (except webpack
) will always be present here.
Config Keys
The following keys are defined by default within every PWA instance. You may mutate or compose with them accordingly.
babel
Type: Object
<br>
Default: Link
Your Babel config object.
css
Type: Object
<br>
Default: Link
Core CSS behavior — see css-loader
for options.
html
Type: Object
<br>
Default: Link
Your HTML plugin configuration — see html-webpack-plugin
for options.
less
Type: Object
<br>
Default: Link
Any less-loader
options — see less-loader
for documentation.
Note: This is the entire loader config; you may need to include the
lessOptions
nested object.
postcss
Type: Object
<br>
Default: Link
Your PostCSS config — you may also use any config file/method that postcss-loader
accepts.
Important: The
postcss.plugins
key cannot be a function!
sass
Type: Object
<br>
Default: Link
Any sass-loader
options — see sass-loader
for documentation.
This object will be used for both .scss
and .sass
file extensions.<br>The .sass
extension will automatically enforce the indentedSyntax
option.
Note: This is the entire loader config; you may need to include the
sassOptions
nested object.
stylus
Type: Object
<br>
Default: Link
Any stylus-loader
options — see stylus-loader
for documentation.
terser
Type: Object
<br>
Default: Link
The options for Terser Plugin.
Note: Expecting UglifyJS? It's no longer maintained!<br>The Terser configuration is nearly identical – simply rename
uglifyOptions
toterserOptions
:+1:
webpack
Type: Function
The main handler for all of PWA!<br>
When you define a custom webpack
, you are not overriding this function. Instead, you are manipulating Webpack's config immediately before PWA executes the build.
Browserslist
The preferred method for customizing your browser targets is thru the browserslist
key within your package.json
file.
Note: When creating a new project with
pwa init
, our recommended config is automatically added for you!
You may choose to change the default values, or use any configuration method that Browserslist accepts.
The resulting array of browser targets will be automatically applied to Autoprefixer, Babel, Bublé, PostCSS, Stylelint, ...etc.
Customizing
Presets and Plugins are just encapsulated config mutations — that's it!
Now, if you want to further customize your PWA build, beyond what your installed Presets & Plugins are giving you, then you can create a pwa.config.js
in your project's root directory.
Note: Your new
pwa.config.js
file should sit alongside yourpackage.json
:thumbsup:
With this file, you may mutate or compose any of the config keys that either PWA or its Plugins exposes to you.
Here is an example custom config file:
// pwa.config.js
const OfflinePlugin = require('offline-plugin');
// Mutate "@pwa/plugin-eslint" config
exports.eslint = function (config) {
config.formatter = require('eslint-friendly-formatter');
};
// Add new PostCSS Plugin
exports.postcss = function (config) {
config.plugins.push(
require('postcss-flexbugs-fixes')
);
};
// Export these pages during "pwa export" command
exports.routes = ['/login', '/register', '/articles/hello-world'];
// Update Webpack config; ENV-dependent
exports.webpack = function (config, env) {
let { production, webpack } = env;
if (production) {
config.plugins.push(
new OfflinePlugin(),
new webpack.DefinePlugin({
MY_API: JSON.stringify('https://api.example.com')
})
);
} else {
config.devServer.https = true;
config.plugins.push(
new webpack.DefinePlugin({
MY_API: JSON.stringify('http://staging.example.com')
})
);
}
};
Credits
A huge thank-you to Jimmy Moon for donating the @pwa
organization on npm! :raised_hands: Aside from being the perfect name, we wouldn't be able to have automatic preset/plugin resolution without a namespace!
Incredible thanks to the giants whose shoulders this project stands on~! :heart:
PWA was originally conceived in 2016 but at that time, it wasn't yet possible to build it with the feature set I had in mind. Since then, an amazing amount of work has been done on Webpack and its ecosystem, which now makes the project goals feasible.
There's no question that PWA takes inspiration from popular CLI applications, like Preact CLI, Vue CLI, and Create React App. They most definitely paved the way. I've used, learned from, and refined my wishlist over years while using these tools. Despite their greatness, I still found a need for a universal, framework-agnostic PWA builder that could unify all these great libraries.
License
MIT © Luke Edwards