UNPKG

timpla

Version:

An optimal website development experience.

604 lines (466 loc) 26.4 kB
<!-- STOP! Don't edit me. Look at local.gulpfile.js/README.md and build from there. --> <div align="center"> <!-- Typedocs will have a copy of ./media in its own folder --> <img src="media/timpla-logo.png" width="182" height="100" /> Your auntie's favourite asset bundler for [server-side] web frameworks. Provides an optimal web-development experience for TypeScript and/or ESNext. Powered by gulp 4 & webpack 4. [![Greenkeeper badge](https://badges.greenkeeper.io/igimanaloto/timpla.svg)](https://greenkeeper.io/) [![Build Status](https://travis-ci.org/igimanaloto/timpla.svg?branch=master)](https://travis-ci.org/igimanaloto/timpla) [![Coverage Status](https://coveralls.io/repos/github/igimanaloto/timpla/badge.svg)](https://coveralls.io/github/igimanaloto/timpla) [![npm version](https://badge.fury.io/js/timpla.svg)](https://badge.fury.io/js/timpla) </div> <!-- TOC --> - [Getting started](#getting-started) - [Installation](#installation) - [Commands](#commands) - [Debugging](#debugging) - [Influence](#influence) - [Configuring Timpla](#configuring-timpla) - [TypeScript](#typescript) - [Disabling TypeScript / TSLint](#disabling-typescript--tslint) - [ESLint](#eslint) - [Disabling ESLint](#disabling-eslint) - [List of available options](#list-of-available-options) - [The timplaHelper object](#the-timplahelper-object) - [Overriding tasks](#overriding-tasks) - [Example: Replacing the html task with Shopify Liquid templating](#example-replacing-the-html-task-with-shopify-liquid-templating) - [Watching more files](#watching-more-files) - [Auto-reloading Timpla based on config file changes](#auto-reloading-timpla-based-on-config-file-changes) - [Specifying a config file on runtime](#specifying-a-config-file-on-runtime) - [Overriding babel & babel-loader options](#overriding-babel--babel-loader-options) - [Enabling HMR and React HMR](#enabling-hmr-and-react-hmr) - [Configuring Webpack](#configuring-webpack) - [Common setups](#common-setups) - [Using Timpla as a static html workflow](#using-timpla-as-a-static-html-workflow) - [Extending the html process](#extending-the-html-process) - [Using Timpla with an existing site (Proxy option)](#using-timpla-with-an-existing-site-proxy-option) - [Common Problems](#common-problems) - [I'm getting a different version of dependency X when I use Timpla!](#im-getting-a-different-version-of-dependency-x-when-i-use-timpla) - [When I build, javascript files don't work!](#when-i-build-javascript-files-dont-work) - [process.cwd() isn't working as it's supposed to!](#processcwd-isnt-working-as-its-supposed-to) - [CAQIAs (Commonly asked questions I ask)](#caqias-commonly-asked-questions-i-ask) - [Developing the plugin](#developing-the-plugin) - [Releasing the plugin](#releasing-the-plugin) <!-- /TOC --> ## Getting started ### Installation Timpla is capable of running ESNext and TypeScript in parallel, to help you migrate or run co-existing source code. You may also configure Timpla to run TypeScript or ESNext only through specific options. We recommend that you install Timpla as a whole first first, and removing bits that you do not need. See [Configuring Timpla](#configuring-timpla) for more info. ```sh # Base install npm i --save-dev timpla webpack @babel/core # Run these too to get Timpla up and running after npx timpla init. # Each of the dependencies here are removeable, so we recommend # installing the whole bunch first to see how everything works. # Slim it down afterwards based on your requirements! npm i --save-dev react react-dom react-hot-loader prettier @babel/preset-env @babel/polyfill @babel/preset-typescript @babel/preset-react @babel/plugin-syntax-dynamic-import eslint eslint-loader babel-eslint eslint-config-prettier eslint-plugin-react tslint-config-prettier typescript tslint tslint-react @types/react @types/react-dom webpack-bundle-analyzer hard-source-webpack-plugin fork-ts-checker-webpack-plugin speed-measure-webpack-plugin # Initialise timpla # Careful: outputs config files and a starter src directory: # .babelrc.js # .eslintrc # tsconfig.json # tslint.json # .prettierrc # .timplaconfig.js npx timpla init # Start the dev-server npx timpla # Build all asset files npx timpla build ``` ### Commands Timpla is a thin wrapper for gulp. You may pass gulp args and options to timpla. Here's a full of commands: ```sh # Timpla starts the development server by default (npx timpla) npx timpla [command] [command] init # inits timpla (copies initial src and config files to project root) initConfig # recreates the .timplaconfig.js file in the project root build # outputs build files clean # cleans the dest directory javascripts # builds js and ts files rev # revs files and outputs a manifest file (designed for html/basic use-cases only) sizeReport # shows a file sizes report svg # combines svg files into one stylesheets # transpiles sass/scss files to css openAnalyzer # opens the bundle analyzer (run this after a build) fonts # copies files from html src to dest images # copies files from images src to dest staticFiles # copies files from static src to dest html # copies files from html src to dest ``` You may add extra tasks through through your timplaconfig.These will be available as `npx timpla [yourcustomtask]`. You can also set up npm scripts to simplify the commands: ```json # You package.json file { "scripts": { "build": "timpla build", "start": "timpla", "tasks": "npx timpla --tasks" } } ``` ### Debugging Enable verbose logs by running any timpla command with env DEBUG=timpla: ```sh DEBUG=timpla timpla [command] ``` To view a breakdown of plugin / loader times, you may also enable it by setting MEASURE=1: ```sh MEASURE=1 timpla [command] ``` This is done through `speed-measure-webpack-plugin`, so you'll need to have that installed in your project. ForkTS Checker breaks when this is run along-side it, so we have decided not to make it separate from the rest of the debug process. Follow the [issue here](https://github.com/stephencookdev/speed-measure-webpack-plugin/issues/56). ## Influence Server-side frameworks like Django, Rails, Craft, Wordpress and Laravel come with templating engines that require access to static assets. Timpla brings back the ol' 'just-output-built-files' approach to asset bundling. This plugin provides a drop-in asset bundling system to complement your favourite server-side framework. It streamlines your build and development processes through the following features: - Bundling JS/TS via Webpack - On-screen ESLint and/or TSLint feedback - Compiling sass and scss files - Bundling svgs via SVGStore - Copying static fonts, images and other files, with support for extending their workflows. - Live-reloading via browsersync and webpack hmr (dev), with support for React Hot Module reloading To be exact, Timpla uses the following technologies: - Gulp - Webpack - BrowserSync - Node-sass - Babel - SVGStore - ESLint (dev-only) - TypeScript - TSLint (dev-only) Timpla started as a fork of [Blendid](https://github.com/vigetlabs/blendid). It has been upgraded to support the latest versions of node, webpack, and @babel! Timpla also provides TSLint and ESLint support for development-mode. Having errors on an overlay are helpful! On production builds, Timpla disables these to speed up compilation times. Please use an alternative process for production builds, if you wish to lint files. ## Configuring Timpla One of Timpla's aims is to be zero-configuration. By default, Timpla is set to run in HTML mode and displays an html page. Browsersync may be used to `mount` on top of your existing project. The most basic Timpla configuration follows: <!-- inject:timpla_config_basic:js --> <!-- endinject --> Tasks such as html, static and images copy files from the src to the dest folder. Use the alternate option to define processing logic. To keep Timpla light, no preprocessing happens for image and other static files. It is left to you to add your preferred workflows. (see alternate configs below) Full configuration is available through .timplaconfig.js. You'll get a copy in your project's root folder after running `npx timpla init`. Timpla exports a helper function called `configure`. Use this to create a timplaconfig object in your `.timplaconfig.js`. As an added benefit, your IDE's intellisense should suggest what options are available! ### TypeScript By default TypeScript and TSLint are enabled. TypeScript functionality is provided through babel-loader and @babel/preset-typescript. TSLint is enabled by default for development mode. `npx timpla` Timpla uses babel-loader with @babel/preset-typescript and [Fork TS Checker Webpack Plugin](https://github.com/Realytics/fork-ts-checker-webpack-plugin) to process TypeScript files. To improve compilation times, Timpla utilises the Fork TS Checker Webpack Plugin to create a separate linting process for TypeScript. Any linting errors won't block the webpack compilation process but will display either in the console or as an overlay on the web page. For production builds, TSLint is disabled. We've found that it is better to run TSLint as a separate production process. ### Disabling TypeScript / TSLint Uninstall the following packages: `fork-ts-checker-webpack-plugin`, `tslint`, and `typescript`. Remove @babel/preset-typescript from .babelrc.js. Finally, set javascripts.development.tslint to false. ### ESLint ESLint is enabled for dev-mode. Timpla requires you to have an eslintrc file in your project directory, as well the eslint-loader package installed. Eslintrc resolution is left to the eslint-loader plugin, which means that it should pick up child-dir eslintrc files. ### Disabling ESLint Set javascripts.development.eslint to false if you don't require it. As eslint-loader is loaded conditionally, you may uninstall it if you don't require eslint. ### List of available options [Full TS documentation](https://igimanaloto.github.io/timpla/interfaces/_lib_timplainterfaces_.ifulltimplaconfig.html) of the timpla config object is available for viewing. An excerpt of the full timpla config follows: <!-- inject:timpla_config:ts --> <!-- endinject --> ### The timplaHelper object The following variables are available from the timplaHelper ```javascript const timplaHelper = { browserSync, // Access the browserSync instance gulp, // use this to create new tasks projectDestPath, // accepts an arbitrary amount of string arguments, concatenates them and returns a full path projectSrcPath, // accepts an arbitrary amount of string arguments, concatenates them and returns a full path timplaConfig, // the full timplaConfig timplaProcess, // useful constants such as IS_DEVELOPMENT, IS_PRODUCTION, INIT_CWD } ``` ### Overriding tasks You may overwrite any listed tasks with an alternate option. Please provide a higher order function (a function returning a function) that accepts a timplaHelper object. You may use these to help write the tasks. The higher order function should signal gulp completion: either through a manual callback call or by returning a gulp stream. If in doubt, always return an Undertaker TaskFunction! A contrived example follows. ```javascript { // ... the rest of your timpla config stylesheets: { // ... the rest of your stylesheets config alternate({ browserSync, gulp, projectDestPath, projectSrcPath, timplaConfig, timplaProcess, }){ const stylesheetsConfig = timplaConfig.stylesheets // access the stylesheets config const paths = { src: projectSrcPath(stylesheetsConfig.src, '**/*.{' + stylesheetsConfig.extensions + '}'), dest: projectDestPath(stylesheetsConfig.dest), } // We must return a higher order function return (cb) => { // if gulp is not being used, cb can be used to call signal completion // dosomethingElse(); // cb() // We can also return a gulp stream instead return gulp .src(paths.src) // finds [your-project]/stylesheets/something.txt .pipe(yourOwnSassProcesser()) .pipe(gulp.dest(paths.dest)) // transfers the file to [your-project]/stylesheets/somethingelse.txt .pipe(browserSync.stream()) // streams changes to browserSync } } } } ``` #### Example: Replacing the html task with Shopify Liquid templating The following example shows how to get Shopify liquid templating support. We'll override the html task to run the html files through liquidr via gulp-liquidr. The same approach can be made to pull in gulp-pug, gulp-haml, gulp-slim and gulp-jinja! ```javascript // make sure to run npm i --save gulp-liquidr first const { configure } = require('timpla') const liquidr = require('gulp-liquidr') const changed = require('gulp-changed') module.exports = configure({ // ... the rest of your file html: { src: 'html', dest: './', // An alternative task may be defined to replace the default alternate({ gulp, env, timplaConfig, browserSync, projectSrcPath, projectDestPath }) { const htmlConfig = timplaConfig.html const paths = { src: [projectSrcPath(htmlConfig.src, '**/*.html')], dest: projectDestPath(htmlConfig.dest), } return () => gulp .src(paths.src) .pipe(changed(paths.dest)) .pipe( liquidr({ root: [projectSrcPath(htmlConfig.src)], data: { accessMeInYourTemplate: 'ishouldwork', }, // other config available from https://www.npmjs.com/package/gulp-liquidr }) ) .pipe(gulp.dest(paths.dest)) .pipe(browserSync.stream()) }, }, }) ``` ### Watching more files Browsersync accepts a `files: [globs]` config - so you may use that to watch other file globs: ```javascript module.exports = { browserSync: { files: ['templates/**/*'], }, } ``` Timpla also sets up a watcher that streams individual task runs to browsersync. Each task can be configured with a list of `extensions` to watch. Provide an empty array to watch all of the files in the tasks' dest folder. For example, the default HTML task watches entire folders. Keep this in mind when overriding the tasks - as long as you reuse browserSync.stream(), you should see changes on-screen. #### Auto-reloading Timpla based on config file changes Timpla fully reloads the dev-server whenever your config files such as babelrc, tsconfig, tslint and eslint files change. This saves you the hassle of having to restart just to test configuration settings! You may also extend the watched files/folders by adding them to .timplaconfig.js > development.timplaWatch. Timpla iterates through these files and sets up reload watchers. You may provide resolved or relative paths. Setting timplaWatch to false disables the reload server. ### Specifying a config file on runtime To specify a timpla config on runtime, you may set the TIMPLA_CONFIG_FILE env like so: `TIMPLA_CONFIG_PATH='./relative/path/to/file' npx timpla [command]` ### Overriding babel & babel-loader options Please use javascripts.babelLoaderOptions.extends to set the babelrc file. Timpla copies the following babelrc file to your project folder. (.babelrc.js) and pre-configures your .timplaconfig.js to pick this up. <!-- inject:babel:js --> <!-- endinject --> ### Enabling HMR and React HMR Hot module reloading is enabled by default. Setting javascripts.development.webpackHotMiddlewareOptions to false disables it. To support react hot module reloading, please install and add react-hot-loader/babel to your .babelrc's plugins. This should be fine even for production builds, as react-hot-loader adds minimal footprint to your code. ## Configuring Webpack Webpack by default uses the following plugins (module.rules): ```sh HardSourceWebpackPlugin # improves build time performance TerserPlugin # uglifies/minifies code SpeedMeasurePlugin # Shows a useful breakdown of Webpack compilation speed. Runs when DEBUG=timpla is set. ForkTsCheckerWebpackPlugin # creates a forked (separated) TSLint server to run alongside Timpla BundleAnalyzerPlugin # provides a visual report of js bundle sizes ``` Each of these plugins are configurable. For example, javascripts.tslint.forkTsCheckerOptions accepts a ForkTsCheckerWebpackPlugin configuration object. In certain scenarios, Timpla lazy-loads plugins. This gives you the flexibility to `remove` packages that you don't need :). For example, setting javascripts.tslint to false disables tslint and loading the module itself. As a result, you may remove tslint and ForkTsCheckerWebpackPlugin from your dependencies. Webpack also uses the following loaders: ``` babel-loader (ESNext and TypeScript support) eslint-loader (dev-only) ``` You have access to the full webpack configuration before it gets fed to Webpack itself. This way, you can add extra loaders you wish to use. Please use the javascripts.customizeWebpackConfig rule in .timplaconfig.js. It accepts a function that returns a configuration object: ```javascript // .timplaconfig.js { // ... the rest of your config file javascripts: { customizeWebpackConfig({ webpackConfig, timplaConfig, timplaProcess, webpack, projectDestPath, projectSrcPath, webpackMerge }) => { const modifiedConfig = { ...webpackConfig } // You can modify the config to how you want it // check if it's prod or development if (env === 'production') { // do this; } if (env === 'development') { // do this } // or even modify rules & plugins... modifiedConfig.plugins = modifiedConfig.plugins.filter(yourFilterFunction) modifiedConfig.module.rules.push(yourOwnRule) // or with the webpack merge plugin, you can even do a webpackMerge(webpackConfig, require('./yourOverrides')) return modifiedConfig } } } ``` ## Common setups ### Using Timpla as a static html workflow Out of the box, Timpla is configured to serve an html page. ### Extending the html process If you wish to build pages with nunjucks or pug, please refer to the samples/html folder. ### Using Timpla with an existing site (Proxy option) More often than never, server-side frameworks run your site on localhost (or a locally configured server). Timpla can mount on your site through BrowserSync's proxy option. To use timpla with an existing site, you may follow the sample browsersync config below. You run your server-side framework and Timpla in a separate process. Use the browserSync option in .timplaconfig.js to set BrowserSync options - it accepts a configuration object. An example of getting it to work with Django/Rails/Laravel follows: ```javascript // .timplaconfig.js const { configure } = require('timpla') const path = require('path') // You would most probably want to react to template changes, you may set extra folders here (relative) const extraWatchFiles = ['templates/**/*'] // The local instance of your site that Timpla will proxy to const proxyTarget = 'http://127.0.0.1:7025' // Access the site via localhost:localhostPort const localhostPort = 5605 // If you are using nginx/htaccess to point a host to a local port // This is useful if CORS headers are set on your local site resources (CDN) const host = 'http://local-new.grabone.co.nz' module.exports = configure({ browserSync: { proxyTarget, files: extraWatchFiles, port: localhostPort, cors: true, proxy: { target: proxyTarget, proxyReq: [ // Additional request headers may be added here function(proxyReq) { proxyReq.setHeader('host', host.replace(/^https?:\/\//i, '')) // grabone authentication requires proper host header to be set proxyReq.setHeader('Access-Control-Allow-Origin', '*') }, ], }, files: files.map(fileGlob => path.join(process.env.PWD, fileGlob)), host, // Prevent browsersync from automatically opening the site (can get annoying pretty quickly) open: false, online: false, }, // ... the rest of your config }) ``` ## Common Problems ### I'm getting a different version of dependency X when I use Timpla! Run `npm i --save/--save-dev [your-package`] to ensure that Webpack picks up your preferred package version. ### When I build, javascript files don't work! We have encountered this happening when `production.rev` is enabled, and swapping that to false. In such cases, please attempt to clear `./node_modules/cache` to force all caches to clear and try again! ### process.cwd() isn't working as it's supposed to! Gulp / Timpla sets the process.env.INIT_CWD to the current projectRoot whereas process.cwd() points to the node_modules/timpla folder. Please ensure that you use INIT_CWD instead to resolve to projectPaths. An example is provided in .timplaconfig.js. ## CAQIAs (Commonly asked questions I ask) <details> <summary>Is this workflow right for me?</summary> <p> If you are working on a server-side web framework like Django, Rails, Laravel, Drupal or Craft - then yes! Timpla works along-side your engine's web templating system by dealing only with building your site assets. Timpla provides live-reloading and a sweet build process - so you can worry less about setup and more about writing code! </p> <p> If you are working on a single page application or a client-side library/framework (e.g. Vue, React and Angular), it's best to go for a client-side boilerplate. The returns will diminish with no-backbone apps. </p> </details> <details> <summary>Can't Webpack do most of what the workflow is doing?</summary> <p> Timpla provides a simple workflow for your assets. Timpla relies on a task runner for static assets and a module bundler for more bundling. </p> <p> A few issues I've encountered with using Webpack for server-side web frameworks: <ul> <li>Webpack hot module reloading support depends on the existence of a middleware plugin. Deciding to use one would require one to spend time learning the ins and outs of the plugin</li> <li>Webpack bundling all your assets means that you have to feed all your html and asset references to webpack.</li> <li>Extracting css via webpack is tailored for Client-side frameworks. I want a hard css file to use with my html pages. Webpack bundles css via mini-css-plugin but also outputs js entry points.</li> </ul> </p> <p> <a href="https://webpack.js.org/guides/integrations/">From the webpack docs</a> <blockquote> Let's start by clearing up a common misconception. webpack is a module bundler like Browserify or Brunch. It is not a task runner like Make, Grunt, or Gulp. Task runners handle automation of common development tasks such as linting, building, or testing your project. Compared to bundlers, task runners have a higher level focus. You can still benefit from their higher level tooling while leaving the problem of bundling to webpack. </blockquote> </p> <p> However, do always take it with a grain of salt. What works for your team should be the option. </p> </details> <details> <summary>Gulp? Task runners are no longer required!</summary> <p>Gulp still solves the issue of running tasks. Instead of polluting your npm run scripts or ad-hoc writing your build scripts, Gulp provides an easy-to-pick-up DSL for writing tasks.</p> <p>Although gulp plugins are long past their debut, most of them are still relevant to our tasking needs.</p> <p>Gulp is useful as it opens up streams to BrowserSync and Webpack.</p> </details> <details> <summary>Gem/plugin/package X provides webpack middleware...</summary> <p> Yes, there are packages, gems or composer plugins that allow injecting webpack via middleware into your asset pipeline. </p> <p> These tools are great, but I've found myself working on different server-side frameworks throughout the years. That's why I've decided to pursue an option that's easy to use and is framework-agnostic. </p> <p> Browsersync is a powerful development tool. It automates live-reloading, file and interaction syncing. Marrying that up with webpack's Hot Module Reloading allows for a solid development environment. </p> </details> <details> <summary>How different is this from Blendid?</summary> <p> After a fork to upgrade dependencies, it was decided to branch out to provide an updated lighter configuration framework. </p> </details> <details> <summary>Does it optimise images or fonts?</summary> <p>Timpla only copies images and fonts to their destination folders. It is recommended that you optimise the images separately, so as not to overload the build process. If you really want to, use the alternate() config.</p> </details> <details> <summary>Why not inject stylesheets?</summary> <p>Critical-css, lesser requests through service workers / caching and css-in-html have benefits too :)</p> </details> <details> <summary>Is this production ready?</summary> <p>We use Timpla for [Grabone](https://grabone.co.nz). We invite you to share ideas, and raise issues if you find any! The added benefit of reusing existing gulp tasks is that they are tested independently, dropping the need to duplicate tests!</p> </details> ## Developing the plugin Files are written in ESNext syntax and transpiled through babel, so program to your heart's content. Dev plugins are loaded only in your environment, so they don't get shipped as part of the main package! ```sh cd [timpla folder] npm install npm run lib:start # starts the tsc watcher, compiling gulpfile.ts on the go. # In a separate terminal cd [your project] npm link timpla ``` When npm-linked, make sure you execute your commands using `timpla`. ## Releasing the plugin 1. Make changes 1. pr > Commit those changes to a separate PR 1. pr > Make sure Travis turns green 1. master > Merge PR 1. master > Bump version in package.json 1. master > npm run lib:changelog (needs to be installed first) 1. master > npm run lib:docs (builds the ts-docs and readme files) 1. master > Commit package.json and docs, README.md and CHANGELOG.md files 1. master > Tag 1. master > Push 1. npm publish