timpla
TypeScript icon, indicating that this package has built-in type declarations

2.1.0 • Public • Published

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 Build Status npm version codecov

Getting started

Timpla aims is to provide you with an excellent web development experience. Timpla focuses on the truly defined parts in every build process: preparing javascript, css and media files for your websites. Three layers of reloading power Timpla: CSS-injection/reloading via BrowserSync (proxy or server), Webpack JS/TS Hot Module Reloading and full dev-server reloads based on core config changes.

Unlike a boilerplate or a full-blown development framework, the rational behind Timpla is that of guiding users with an undiscriminating base. You bring your own dependencies to enhance and/or extend the core Timpla build pipeline.

Timpla maintains its own side of the fence, while you focus on the more important aspects of your workflow, or better yet - getting straight into work!

You may wonder why bother with Timpla if Webpack does everything. Timpla by no means undermines what Webpack is already able to achieve! However, in our quest to find a sweet development environment, we've come to realise that forcing everything through Webpack yields painful load and reload times. Timpla's performance and tooling in a decently sized project is something we're proud of!

Installation

We recommend that you install Timpla as a whole first first, and removing bits that you do not need. See Configuring Timpla for more info.

# Base install 
npm i --save-dev timpla webpack @babel/core
 
# Timpla allows you to bring in your own versions of the following modules! 
npm i --save @babel/polyfill @babel/preset-env @babel/preset-react @hot-loader/react-dom babel-eslint eslint eslint-config-prettier eslint-loader eslint-plugin-react hard-source-webpack-plugin prettier react react-dom react-hot-loader speed-measure-webpack-plugin webpack-bundle-analyzer node-sass
 
# Install typescript dependencies as well. You may skip this part of you only wish to build JS scripts. 
npm i --save @babel/preset-typescript @types/react @types/react-dom typescript tslint tslint-config-prettier tslint-react fork-ts-checker-webpack-plugin
 
# Initialise timpla 
# Careful: outputs config files and a starter src directory: 
#  babel.config.js 
#  .eslintrc 
#  .prettierrc 
#  .timplaconfig.js 
# The following are generated for TS projects 
#  tsconfig.json 
#  tslint.json 
 
# For TypeScript support (DEFAULT) 
npx timpla init
 
# For JS-only, disabling TypeScript support 
npx timpla initJS
 
# Start the dev-server 
npx timpla
 
# Build all asset files 
npx timpla build

The Timpla-way of thinking

Timpla is a process-driven tool. Each asset stream holds a pre-defined and parallelised part in the Timpla build chain. The following assets are built in parallel:

  • Sass files are compiled into css files, so you may load it as normal in your templates.
  • SVG files are combined into single-svgs for use, either via direct insertion into your templates, or your javascript-driven loading.
  • Image and static asset files are simply copied, but the processes are extendible, wherein you can tap into the build-stream and introduce your own pre-processing tasks.
  • Javascript and/or TypeScript files bundled using Webpack.
  • Useful tooling such as file size reports, file-revving, bundle analysis and Webpack performance measurements are made available to further enhance your development process.

In every dev or production build, prebuild and postbuild hooks are available for you to enhance the Timpla process. Apart from the javascript task, you may replace how each asset-type is processed via Timpla's alternateable tasking system.

Timpla also allows you to register extra tasks via Gulp. These may be added to the pre or post parts of the build process.

Timpla nukes the destination folder for every dev or production start. This ensures that you treat the src directory as the one true source for raw source files.

Commands

# 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 font 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 

These will be available as npx timpla [yourcustomtask].

You can also set up npm scripts to simplify the commands:

# 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:

DEBUG=timpla timpla [command]

To view a breakdown of plugin / loader times, you may also enable it by setting MEASURE=1:

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.

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 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.

Timpla started as a fork of Blendid, and continues to carry the torch for providing awesome server-side-driven dev experiences.

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:

// @ts-check
 
/**
 * The @type annotations are harmless, vscode might just make your life easier
 * by adding intellisense support whilst you type!
 */
 
const { configure } = require('timpla')
 
module.exports = configure({
  javascripts: {
    /** @type { import("timpla").ITimplaWebpackEntryHook } */
    entry: ({ resolve: r }) => ({
      'js-index': r('./js-index.jsx'),
      'ts-index': r('./ts-index.tsx'),
      'ts-error': r('./ts-error.ts'),
    }),
 
    /** @type { import("timpla").ITimplaCustomizeWebpackConfig } */
    customizeWebpackConfig(w) {
      // The following helpers are available to aid you in extending the webpack config.
      const {
        projectDestPath,
        projectSrcPath,
        timplaConfig,
        timplaProcess,
        webpack,
        webpackConfig,
        webpackMerge,
      } = w
      return w.webpackConfig
    },
  },
 
  // To get intellisense for more complex configs, follow the jsdoc example below:
  // html: {
  //   /** @type { import("timpla").ITimplaHofTask } */
  //   alternate: p => {
  //     p.timplaProcess
  //   },
  // },
 
  //  additionalTasks: {
  //    /** @param p { import("timpla").ITimplaHelper } */
  //    initialize: p => {},
  //    development: {
  //      /** @type { import("timpla").ITimplaHofTask } */
  //      prebuild(tp) {},
  //    },
  //  },
})
 

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 for reach of the asset tasks 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!

List of available options

Full TS documentation of the timpla config object is available for viewing. An excerpt of the full timpla config follows:

 
export interface IFullTimplaConfig {
  /** The base folder to include for compilation. Defaults to `./src`. */
  src: string
  /** The base folder to output bundled files to. Defaults to `./dest`. */
  dest: string
  /*
   * Instructs timpla on how to run the staticFiles task.
   * Set to `false` to disable the task.
   */
  staticFiles:
    | false
    | {
        /** The folder to include for compilation. Defaults to `static`. */
        srcstring
        /** The folder to output bundled files to. Defaults to `./`. */
        deststring
        /** Options to pass to the gulp src task. This setting is ignored when an alternate task is provided. */
        srcOptionsSrcOptions
        /** Options to pass to the gulp dest task. This setting is ignored when an alternate task is provided. */
        destOptionsDestOptions
        /** A custom task to override the default with. */
        alternate?: ITimplaHofTask
      }
  /**
   * Instructs timpla on how to run the clean task.
   * Set to `false` to disable the task.
   */
  clean:
    | false
    | {
        /**
         * Extra files and folders to `clean` before Timpla runs the dev-server or full-build.
         * `Please take great care when specifying your own patterns`
         * Accepts an array of globs. Defaults to the project `dest` directory.
         */
        patterns?: string | ReadonlyArray<string>
        /** Options to pass to the del function. */
        delOptions?: Options
      }
 
  /** Watch task options */
  watch: {
    /**
     * Gulp watch options.
     * Accepts gulp watch options for all tasks,
     * or a keyed-list of watch-options to apply to tasks individually.
     * - E.g. gulpWatch.svg = {} sets specific configs for the svg task.
     */
    gulpWatch: {
      [name: string]: any
    }
  }
 
  /**
   * Instructs timpla on how to run the stylesheets task.
   * Set to `false` to disable the task.
   */
  stylesheets:
    | false
    | {
        /** The folder to include for compilation. Defaults to `stylesheets`. */
        srcstring
        /** The folder to output bundled files to. Defaults to `stylesheets`. */
        deststring
        /** Options to pass to the gulp src task. This setting is ignored when an alternate task is provided. */
        srcOptionsSrcOptions
        /** Options to pass to the gulp dest task. This setting is ignored when an alternate task is provided. */
        destOptionsDestOptions
        /** [Cssnano](https://www.github.com/cssnano/cssnano) options */
        cssnanoOptionsCssNanoOptions
        /** [Autoprefixer](https://github.com/postcss/autoprefixer#options) options */
        autoprefixerOptionsautoprefixer.Options
        /** [PostCSS](https://github.com/postcss/postcss#options) options */
        postcssOptionspostcss.ProcessOptions
        /** [Gulp/Node sass](https://www.npmjs.com/package/gulp-sass) options */
        sassOptionsINodeSassOptions
        /** A list of extensions for BrowserSync to watch. Defaults to `['sass', 'scss', 'css']`. */
        extensionsstring[]
        /** Development-specific stylesheets options */
        development{
          /** Defaults to `true`. */
          sourceMapboolean
        }
        /** Production-specific stylesheets options */
        production{
          /** Defaults to `false`. */
          sourceMapboolean
        }
        /** A custom task to override the default with. */
        alternate?: ITimplaHofTask
      }
  /**
   * Instructs timpla on how to run the fonts task.
   * Set to `false` to disable the task.
   */
  fonts:
    | false
    | {
        /** The folder to include for compilation. Defaults to `fonts`. */
        srcstring
        /** The folder to output bundled files to. Defaults to `fonts`. */
        deststring
        /** Options to pass to the gulp src task. This setting is ignored when an alternate task is provided. */
        srcOptionsSrcOptions
        /** Options to pass to the gulp dest task. This setting is ignored when an alternate task is provided. */
        destOptionsDestOptions
        /** A list of extensions for BrowserSync to watch. Defaults to `['woff2', 'woff', 'eot', 'ttf', 'svg']`. */
        extensionsstring[]
        /** A custom task to override the default with. */
        alternate?: ITimplaHofTask
      }
  /**
   * Instructs timpla on how to run the images task.
   * Set to `false` to disable the task.
   */
  images:
    | false
    | {
        /** The folder to include for compilation. Defaults to `images`. */
        srcstring
        /** The folder to output bundled files to. Defaults to `images`. */
        deststring
        /** Options to pass to the gulp src task. This setting is ignored when an alternate task is provided. */
        srcOptionsSrcOptions
        /** Options to pass to the gulp dest task. This setting is ignored when an alternate task is provided. */
        destOptionsDestOptions
        /** A list of extensions for BrowserSync to watch. Defaults to `['jpg', 'jpeg', 'png', 'svg', 'gif', 'webp']`. */
        extensionsstring[]
        /** A custom task to override the default with. */
        alternate?: ITimplaHofTask
      }
  /**
   * Instructs timpla on how to run the svg task.
   * Set to `false` to disable the task.
   */
  svg:
    | false
    | {
        /** Options for the gulp-svg plugin. */
        svgstoreIGulpSvgStore
        /** The output filename for the concatanted svg file. Defaults to `icons.svg`. */
        outputNamestring
        /** The folder to include for compilation. Defaults to `svg`. */
        srcstring
        /** The folder to output bundled files to. Defaults to `images`. */
        deststring
        /** Options to pass to the gulp src task. This setting is ignored when an alternate task is provided. */
        srcOptionsSrcOptions
        /** Options to pass to the gulp dest task. This setting is ignored when an alternate task is provided. */
        destOptionsDestOptions
        /** A custom task to override the default with. */
        alternate?: ITimplaHofTask
      }
  /**
   * Instructs timpla on how to run the html task.
   * Set to `false` to disable the task.
   */
  html:
    | false
    | {
        /** The folder to include for compilation. Defaults to `html`. */
        srcstring
        /** The folder to output bundled files to. Defaults to ``. */
        deststring
        /** Options to pass to the gulp src task. This setting is ignored when an alternate task is provided. */
        srcOptionsSrcOptions
        /** Options to pass to the gulp dest task. This setting is ignored when an alternate task is provided. */
        destOptionsDestOptions
        /** A custom task to override the default with. */
        alternate?: ITimplaHofTask
      }
  /**
   * Instructs timpla on how to run the javascripts task.
   */
  javascripts: {
    /** The folder to include for compilation. Defaults to `javascripts`. */
    src: string
    /** The folder to output bundled files to. Defaults to `javascripts`. */
    dest: string
    /** A list of extensions for BrowserSync to watch and Webpack to load. Defaults to `['js', 'jsx', 'ts', 'tsx']`. */
    extensions: string[]
    /**
     * The webpack entry config.
     * Accepts a Timpla-flavoured entry function or a webpack entry object.
     * Webpack is configured to use the projectRoot as the cwd.
     * Files you point in the entry config need to have the relative path to the javascripts folder.
     * Use the `resolve` helper to resolve files to the js src folder.
     */
    entry: ITimplaWebpackEntry
    /**
     * Instructs webpack the base path for all the assets within your application.
     * Defaults to the `javascripts.dest` path. e.g. `/javascripts`
     */
    publicPath?: string
    /**
     * Options for the babelLoader. Accepts babel-loader specific as well as Babel options.
     * For more advanced use-cases, e.g. manually specifying the babel config file to use.
     * Defaults to
     * - `cacheDirectory: true`
     * - `cacheCompression: false`
     * - `root: your project root`
     */
    babelLoaderOptions: IBabelLoaderOptions
    /**
     * Accepts [webpackBundleAnalyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) options.
     * Set to `false` to disable webpackBundleAnalyzer.
     * Defaults to
     * - `statsFilename: timpla-webpack-stats.json`
     * - `openAnalyzer: true if Timpla hasn't fully reloaded.`
     */
    webpackBundleAnalyzerOptions: false | BundleAnalyzerPlugin.Options
    /**
     * A customisation function that allows modifying the generated
     * webpack config before it is passed to webpack.
     */
    customizeWebpackConfig: ITimplaCustomizeWebpackConfig
 
    /**
     * Instructs timpla on how to run Webpack in production.
     */
    production: {
      /**
       * The webpack devtool to use.
       * Defaults to `source-map`.
       */
      devtool: webpack.Options.Devtool
      /**
       * Timpla uses Hard Source to improve production build times.
       * Set to `false` to disable the plugin.
       * Accepts [hard-source-webpack-plugin](https://github.com/mzgoddard/hard-source-webpack-plugin) options.
       */
      hardSourceOptions: false | IHardSourceWebpackPluginOptions
      /**
       * [Terser Webpack Plugin](https://github.com/webpack-contrib/terser-webpack-plugin) options.
       * Timpla uses Terser to uglify the bundled JS files.
       * UglifyJS2 is outdated, and Terser is its successor.
       */
      terserConfig: Partial<{}>
    }
 
    /**
     * Enables or disables TypeScript support for Timpla. Default: `true`
     * When upgrading a Timpla JS project, please install the required dependencies:
     * - `npm i --save-dev typescript tslint fork-ts-checker-webpack-plugin`
     */
    useTypeScript: boolean
  }
 
  /**
   * Configures the BrowserSync task.
   * Accepts [browser-sync](https://browsersync.io/docs/options) options.
   */
  browserSync: false | ITimplaBrowserSyncOptions
 
  /** Instructs Timpla on after-build tasks. */
  production: {
    /**
     * Opens a specified target in an app after all the core-tasks run.
     * Uses the open package to achieve this.
     * Defaults to `false`.
     */
    open: false | IOpenOptions
    /**
     * Enables file-reving (hashed filenames).
     * Timpla revs your files and generates a json manifest file.
     * You'll need to write your own mapper to pick up the filenames from the rev manifest file.
     * Defaults to `false`.
     */
    rev: boolean
  }
 
  /**
   * Instructs timpla on how it runs in dev-mode.
   */
  development: {
    /**
     * Opens a specified target in an app after all the core-tasks run.
     * Uses the open package to achieve this.
     * Defaults to `false`.
     */
    open: false | IOpenOptions
    /**
     * Extra files and folders for Timpla to watch.
     * When these change, Timpla reloads the dev-server.
     * Set to `false` to disable this option.
     * Defaults to `true`.
     */
    timplaWatch: boolean | string[]
 
    /**
     * The webpack devtool to use.
     * Defaults to `cheap-module-eval-source-map`.
     */
    devtool: webpack.Options.Devtool
 
    /**
     * Accepts [Webpack Hot Middleware](https://github.com/webpack-contrib/webpack-hot-middleware) client options.
     * Set to `false` to disable [hot module reloading](https://webpack.js.org/concepts/hot-module-replacement/).
     * Defaults to
     * - `reload: true`
     * - `quiet: true`
     * - To configure the middleware itself, use `development.middlewareConfig.webpackHotMiddleware`.
     */
    webpackHotMiddlewareClient: IWebpackHotMiddlewareClientOptions | false
 
    /**
     * Toggle default BrowserSync middlewares added by Timpla.
     *
     * Set to `false` to disable all default middleware from being added. Err on the side of caution as this may break browserSync and JS from working altogether.
     *
     * By default, timpla runs as a SPA, so it requires the use of connectHistoryApiFallbackMiddleware so that requests
     * may be routed correctly.
     *
     * When browserSync is configured to run in proxy-mode,
     * we disable connectHistoryApiFallbackMiddleware to ensure that all requests go through to your proxied server as normal.
     */
    middlewareConfig:
      | false
      | {
          /**
           * Accepts webpackDevMiddleware config.
           * - To disable, set the value to `false`.
           */
          webpackDevMiddleware?: false | Partial<IWebpackDevMiddlewareOptions>
 
          /**
           * Accepts webpackHotMiddleware config.
           * This only sets the middleware config.
           * - To configure the client, Use `development.webpackHotMiddlewareClient`.
           * - To disable, set the value to `false`.
           */
          webpackHotMiddleware?: false | Partial<IWebpackHotMiddlewareOptions>
 
          /**
           * Accepts connectHistoryApiFallbackMiddleware config.
           * - To disable, set the value to `false`.
           */
          connectHistoryApiFallbackMiddleware?: false | IConnectHistoryApiFallbackOptions
        }
 
    /** Disables the `fallback server config` that Timpla uses when no browserSync config is supplied. */
    disableServerFallback: boolean
    /**
     * In dev-mode, Timpla supports ESLint via the ESLint loader.
     * By default, Timpla requires the eslint package to be installed.
     * - `npm i --save-dev eslint`
     * - Set to `false` to disable ESLint.
     */
    eslint:
      | false
      | {
          /*
           * Accepts [eslint-loader](https://github.com/webpack-contrib/eslint-loader) & [eslint](http://eslint.org/docs/developer-guide/nodejs-api#cliengine) options.
           */
          esLintLoaderOptions: IESLintLoaderOptions
        }
 
    /**
     * In dev-mode, Timpla uses Fork TS Checker, a webpack plugin that runs typescript export type checker on a separate process.
     * By default, Timpla requires dependent packages to be installed.
     * - `npm i --save-dev typescript tslint fork-ts-checker-webpack-plugin`
     * - Set to `false` to disable the plugin, and TSLint support.
     */
    tslint:
      | {
          /** Accepts [Fork TS Checker](https://github.com/Realytics/fork-ts-checker-webpack-plugin) options. */
          forkTsCheckerOptions: IForkTsCheckerOptions
        }
      | false
  }
 
  /** Instructs Timpla on additional tasks */
  additionalTasks: {
    /** An init function that runs before all the other Timpla. */
    initialize?: (timplaHelper: ITimplaHelper) => any
    /** Instructs Timpla to add extra `development tasks`. */
    development: {
      /** Adds an extra development `prebuild task`. Accepts a `higher-order-function`, i.e. returns a Gulp/Undertaker TaskFunction. */
      prebuild?: ITimplaHofTask
      /** Adds an extra development `postbuild task`. Accepts a `higher-order-function`, i.e. returns a Gulp/Undertaker TaskFunction. */
      postbuild?: ITimplaHofTask
    }
    /** Instructs Timpla to add extra `production tasks`. */
    production: {
      /** Adds an extra production `prebuild task`. Accepts a `higher-order-function`, i.e. returns a Gulp/Undertaker TaskFunction. */
      prebuild?: ITimplaHofTask
      /** Adds an extra production `postbuild task`. Accepts a `higher-order-function`, i.e. returns a Gulp/Undertaker TaskFunction. */
      postbuild?: ITimplaHofTask
    }
  }
}
 
/** The partialised Timpla Config so users' may opt in to only customise what they wish to. */
export type ITimplaConfig = IRecursivePartial<IFullTimplaConfig>
 

The timplaHelper object

The following variables are available from the timplaHelper

const timplaHelper = {
  browserSync, // Access the browserSync instance.
  gulp, // A gulp instance that can be used to register new tasks.
  projectDestPath, // Resolves a list of strings to the project's dest path.
  projectSrcPath, // Resolves a list of strings to the project's src path.
  timplaConfig, // The full timplaConfig
  timplaProcess, // Provides useful constants such as isDevelopment, isProduction and INIT_CWD (your project's resolved base path).
}

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

You may use javascripts.babelLoaderOptions to fully control how the webpack babel-loader works.

Babel config resolution is set to default, so babel.config.js and babelrc files are picked up by Timpla. We recommend using babel.config.js as opposed to a babelrc so that npm linked modules are also picked up by babel-loader, via Babel's root config resolution mechanism.

Timpla copies the following babel config file to your project folder. (babel.config.js) and pre-configures your .timplaconfig.js to pick this up.

const debug = process.env.DEBUG === 'timpla'
const isProduction = process.env.NODE_ENV === 'production'
 
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        // usage automatically injects @babel/polyfill, so older browsers may still work
        // see https://babeljs.io/docs/en/babel-polyfill
        // We're only loading this for production builds so development compilation times are faster!
        ...(isProduction && { useBuiltIns: 'usage' }),
        debug,
      },
    ],
    '@babel/preset-typescript',
    '@babel/preset-react',
  ],
  plugins: ['react-hot-loader/babel'],
}
 

Configuring Webpack

Webpack by default uses the following plugins (module.rules):

# HardSourceWebpackPlugin - disabled for now, as caching may break babel-loader 7.2 builds. 
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/can be disabled. For example, javascripts.tslint.forkTsCheckerOptions accepts ForkTsCheckerWebpackPlugin configuration options.

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:

// .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 (timplaProcess.isProduction) {
        // do this;
      }
      if (timplaProcess.isDevelopment) {
        // 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
    }
  }
}

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.

{
  // ... 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
      }
    }
  }
}

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.

Timpla uses babel-loader with @babel/preset-typescript and 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

If you followed the installation instructions above, please uninstall the following packages: fork-ts-checker-webpack-plugin, tslint, and typescript. Remove @babel/preset-typescript from babel.config.js. Finally, set javascripts.useTypeScript to false.

Disabling TSLint

Please set development.tslint to false. You may also opt to uninstall tslint and fork-ts-checker-webpack-plugin as these won't be loaded by Timpla anymore.

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 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.

The three levels of reloading

BrowserSync injection and reloading

Timpla uses BrowserSync to inject stylesheets and notify the browser of full-reloads when files change. Timpla pre-configures the watched files for you, but you may also extend it through the files: [globs] config.

module.exports = {
  browserSync: {
    files: ['when_this_changes_reload_the_page/**/*'],
  },
}

When writing your own tasks, Timpla Helper passes the browserSync instance so you may stream(inject) your changes on the fly!

Configuring BrowserSync middleware

Timpla uses webpack-hot-middleware, webpack-dev-middleware and connect-history-api-fallback by default. Each of these accept config objects, via development.middlewareConfig. You may disable any of them by passing false to their config key, e.g. development.middlewareConfig.webpackHotMiddleware: false. If you wish to disable everything, then set middlewareConfig to false.

Hot module reloading + React HMR

Any files you mark as hot won't cause the browser to reload. This is useful when you need to keep the browser in its current state.

Hot module reloading is enabled by default. Setting development.webpackHotMiddlewareClient or development.middlewareConfig.webpackHotMiddleware to false disables it.

To support react hot module reloading, please install and add react-hot-loader/babel to your babel.config.js's plugins. This should be fine even for production builds, as react-hot-loader adds minimal footprint to your code.

Dev-server reloading based on config file changes

Timpla fully reloads the dev-server whenever your config files such as babel config, 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 development.timplaWatch to false disables the reload server.

Common setups

Using Timpla as a static html workflow

Out of the box, Timpla is configured to serve an html page.

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:

// .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
})

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!

// 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())
    },
  },
})

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.

Timpla doesn't pick up changes to my eslintrc

This is a known issue with eslint-loader. Ensure that cache is turned off whilst a patch is being worked on. Alternatively, you may specify your own eslint caching strategy via cacheIdentifer.

When I build, javascript files don't work!

We have encountered this happening whenever you switch the production.rev setting. Please clear ./node_modules/cache to force-clear all caches 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.

Tips for working with npm linked dependencies

Ensure that you are using a babel.config.js. This will allow babel-loader to process the linked module files (TypeScript and ESNext).

  1. npm link yourmodule
  2. set up a module.resolve rule for webpack e.g. yourmodule$: path.resolve(__dirname, 'node_modules/yourmodule/src/index.ts')

CAQIAs (Commonly asked questions I ask)

Is this workflow right for me?

If you are working on a server-side web framework like Django, Rails, Laravel, Shopify, Wordpress, 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!

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.

Help! Running the server redirects all requests to index.html!

In your .timplaconfig.js config, please set development.middlewareConfig.connectHistoryApiFallbackMiddleware to true. Alternatively, providing a browsersync.proxy config disables this automatically for you.

BrowserSync serverStatic option doesn't work! I'm not able to see my statically served files when visiting their urls.

Please make sure that the `browserSync.serveStatic` uses absolute paths. You may use `path.join(__dirname, [youroutdir])` for example to get the full path based on the location of your config.

Gulp? Task runners are no longer required!

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.

Although gulp plugins are long past their debut, most of them are still relevant to our tasking needs.

Gulp is useful as it opens up streams to BrowserSync and Webpack.

Gem/plugin/package X provides webpack middleware...

Yes, there are packages, gems or composer plugins that allow injecting webpack via middleware into your asset pipeline.

These tools are great, but often do we care less the older project dev environments get. By delegating the `core` to Timpla, we hope that more focus is put on extending, rather than maintaining!

Also, we often get into the framework-exclusivity. Think of Timpla as a consistent tool to bring over to projects.

How different is this from Blendid?

After a fork to upgrade dependencies, it was decided to branch out to provide an updated and extensive configuration framework.

Does it optimise images or fonts?

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.

Why not inject stylesheets?

Critical-css, lesser requests through service workers / caching and css-in-html have benefits too :)

Is this production ready?

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!

Developing the plugin

Files are written in TypeScript, 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!

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
  2. pr > Commit those changes to a separate PR
  3. pr > Make sure Travis turns green
  4. master > Merge PR
  5. master > Bump version in package.json
  6. master > npm run lib:changelog (needs to be installed first)
  7. master > npm run lib:docs (builds the ts-docs and readme files)
  8. master > Commit package.json and docs, README.md and CHANGELOG.md files
  9. master > Tag
  10. master > Push
  11. npm publish

Package Sidebar

Install

npm i timpla

Weekly Downloads

94

Version

2.1.0

License

MIT

Unpacked Size

1.53 MB

Total Files

278

Last publish

Collaborators

  • igikorn