@epublishing/grunt-epublishing 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bumpedrc +14 -0
- package/.eslintrc +6 -0
- package/LICENSE +18 -0
- package/README.md +113 -0
- package/__mocks__/@epublishing/get-gem-paths.js +8 -0
- package/etc/banner.txt +6 -0
- package/etc/eslintrc.json +49 -0
- package/lib/base-config.js +34 -0
- package/lib/base-tsconfig.js +14 -0
- package/lib/cli-flags.js +42 -0
- package/lib/configure-postcss.js +18 -0
- package/lib/configure-sass.js +24 -0
- package/lib/configure-webpack.js +192 -0
- package/lib/gen-tsconfig.js +64 -0
- package/lib/init-jade-config.js +91 -0
- package/lib/merge-configs.js +89 -0
- package/lib/webpack-console-timer.js +23 -0
- package/package.json +94 -0
- package/tasks/__tests__/gen-tsconfig.test.js +74 -0
- package/tasks/clean-tsconfig.js +37 -0
- package/tasks/gen-tsconfig.js +38 -0
- package/tasks/jade.js +72 -0
- package/tasks/npm-install.js +73 -0
- package/tasks/watch-all.js +49 -0
package/.bumpedrc
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
files:
|
|
2
|
+
- package.json
|
|
3
|
+
- package-lock.json
|
|
4
|
+
plugins:
|
|
5
|
+
postrelease:
|
|
6
|
+
Committing new version:
|
|
7
|
+
plugin: bumped-terminal
|
|
8
|
+
command: 'git add package* && git commit -m "Version bump to v$newVersion"'
|
|
9
|
+
Publishing tag to Bitbucket:
|
|
10
|
+
plugin: bumped-terminal
|
|
11
|
+
command: 'git tag v$newVersion && git push && git push --tags'
|
|
12
|
+
Publishing to NPM:
|
|
13
|
+
plugin: bumped-terminal
|
|
14
|
+
command: npm publish .
|
package/.eslintrc
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Copyright 2017 ePublishing, Inc.
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
4
|
+
this software and associated documentation files (the "Software"), to deal in the
|
|
5
|
+
Software without restriction, including without limitation the rights to use,
|
|
6
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
7
|
+
Software, and to permit persons to whom the Software is furnished to do so,
|
|
8
|
+
subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
14
|
+
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
15
|
+
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
16
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
17
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
18
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# grunt-epublishing
|
|
2
|
+
|
|
3
|
+
> Automated front-end tasks for ePublishing Jade and client sites.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
This plugin requires Grunt v1.0.0.
|
|
7
|
+
|
|
8
|
+
If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins.
|
|
9
|
+
|
|
10
|
+
NodeJS and Grunt-Cli must be installed before you proceed.
|
|
11
|
+
|
|
12
|
+
If you are on Mac OS X and use homebrew. The following commands will get you ready to run grunt.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
brew install node
|
|
17
|
+
npm install -g grunt-cli
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
* To get started with using Grunt Jade you first need the right files within the project you wish to use this plugin. You will create a package.json and gruntfile.js in the root of your application. (unless someone has already done this)
|
|
21
|
+
|
|
22
|
+
* The gruntfile should contain the following code which enables the default jade task:
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
module.exports = function(grunt) {
|
|
27
|
+
// Load the grunt jade task
|
|
28
|
+
grunt.loadNpmTasks('@epublishing/grunt-epublishing');
|
|
29
|
+
|
|
30
|
+
// Run jade-default Grunt Task
|
|
31
|
+
grunt.registerTask('default', ['jade']);
|
|
32
|
+
};
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
* The package.json file will contain the name of your site, version, and required dependencies that get installed before you can run "grunt". This file would look similar to this, and includes the actual required dependencies for Grunt Jade:
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"name": "jade-labs",
|
|
41
|
+
"version": "0.1.0",
|
|
42
|
+
"description":"jade-labs",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git@bitbucket.org:epub_dev/jade"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"grunt": "^1.0.0",
|
|
49
|
+
"grunt-jade": "git+ssh://git@bitbucket.org:epub_dev/grunt-jade.git"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
* Install the npm modules that this Grunt task depends on. To do this, run the following command in the application you wish to use this plugin.
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
npm install
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Awesome! You are ready to run Grunt in the command line and watch Grunt Jade start working on your front-end assets. Grunt Jade will concatenate and minify the SCSS and Javascript. Here is what `grunt-jade` does when you run `grunt`:
|
|
61
|
+
|
|
62
|
+
1. `set-jade-paths`: Resolve the locations of the active Jade gem, Jade child gem (if there is one), and site directories, making them available as config variables to subsequent tasks.
|
|
63
|
+
1. `npm-install`: Install Node module dependencies in Jade and child gem locations if `package.json` files are present.
|
|
64
|
+
1. `clean`: Clean (delete) previously compiled assets, if any.
|
|
65
|
+
1. `babel`: Transpile standalone ES2015 scripts into public/javascripts/app with Babel.
|
|
66
|
+
1. `concat`: Concatenate our default JS assets into a single script, `jade.default.js`.
|
|
67
|
+
1. `uglify`: Minify the above script.
|
|
68
|
+
1. `webpack`: Compile configured modules in `app/js` and bundle them with their dependencies using Webpack, saving them to `public/javascripts/app/bundle`.
|
|
69
|
+
1. `sass`: Compile SCSS stylesheets to CSS using `libsass`.
|
|
70
|
+
1. `bless`: _(disabled by default)_ Split compiled stylesheets into chunks in order to comply with MSIE 9's stylesheet selector limit.
|
|
71
|
+
|
|
72
|
+
### Custom Tasks
|
|
73
|
+
|
|
74
|
+
`grunt-jade` provides a couple of custom Grunt tasks that run as part of the default task set:
|
|
75
|
+
|
|
76
|
+
+ `set-jade-paths` makes Grunt's configuration aware of the ePublishing gems that a site depends on.
|
|
77
|
+
+ Spawns a [Ruby script](./tasks/get-jade-gems.rb) as a subprocess that does the following things:
|
|
78
|
+
+ Sets up Bundler and loads the site's RubyGems dependencies.
|
|
79
|
+
+ Prints a list of gems whose names match the regular expression `/jade/` to stdout, as a JSON array.
|
|
80
|
+
+ Parses the JSON array and populates `grunt.config.paths` with entries corresponding to:
|
|
81
|
+
+ Jade's installation path as `paths.jade`
|
|
82
|
+
+ The child gem's installation path (if any) as `paths.jadechild`
|
|
83
|
+
+ Various relative asset directory paths (i.e. `paths.js_src`, `paths.css`, etc.)
|
|
84
|
+
+ `npm-install` iterates through the paths populated by `set-jade-paths` looking for package.json files.
|
|
85
|
+
+ If any results are found, it does the following for each:
|
|
86
|
+
+ Switches the current working directory to the parent folder of the current package.json result.
|
|
87
|
+
+ Runs `npm install` as a subprocess and waits for it to complete.
|
|
88
|
+
+ When all results have been processed, the task changes the working directory back to the site's root.
|
|
89
|
+
+ `watch-all` runs `grunt watch` and `grunt webpack --watch` at the same time in subprocesses
|
|
90
|
+
|
|
91
|
+
## Release Management
|
|
92
|
+
|
|
93
|
+
`grunt-epublishing` installs the [bumped](https://bumped.github.io/) package as a development dependency, and its package.json file provides an NPM script that can be used to increment release versions, commit, tag, push, and publish to NPM in a single step:
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
# Example (bump patch version):
|
|
97
|
+
$ npm run release patch
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Local Development
|
|
101
|
+
|
|
102
|
+
If you're working on new build system features, you can link your local clone of grunt-epublishing into a Jade site like
|
|
103
|
+
so:
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
# Inside the grunt-epublishing directory:
|
|
107
|
+
npm link
|
|
108
|
+
|
|
109
|
+
# Change directories to the site:
|
|
110
|
+
cd ../jade-site
|
|
111
|
+
|
|
112
|
+
npm link @epublishing/grunt-epublishing
|
|
113
|
+
```
|
package/etc/banner.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
[0;1;34;94m┌───────────────[0;34m──────────────┐[0m
|
|
2
|
+
[0;1;34;94m│┳━┓┳━┓┳[0m [0;34m┓┳━┓┳[0m [0;34mo┓━┓┳[0m [0;34m┳o[0;37m┏┓┓┏━┓│[0m
|
|
3
|
+
[0;34m│┣━[0m [0;34m┃━┛┃[0m [0;34m┃┃━┃┃[0m [0;37m┃┗━┓┃━┫┃┃┃┃┃[0m [0;37m┳│[0m
|
|
4
|
+
[0;34m│┻━┛┇[0m [0;34m┇[0;37m━┛┇━┛┇━┛┇━━┛┇[0m [0;37m┻┇[0;1;30;90m┇┗┛┇━┛│[0m
|
|
5
|
+
[0;37m└───────────────[0;1;30;90m──────────────┘[0m
|
|
6
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": [ "eslint:recommended", "plugin:react/recommended" ],
|
|
3
|
+
"parserOptions": {
|
|
4
|
+
"ecmaVersion": 6,
|
|
5
|
+
"sourceType": "module",
|
|
6
|
+
"ecmaFeatures": {
|
|
7
|
+
"impliedStrict": true,
|
|
8
|
+
"jsx": true,
|
|
9
|
+
"modules": true
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"env": {
|
|
13
|
+
"browser": true,
|
|
14
|
+
"node": true,
|
|
15
|
+
"amd": true,
|
|
16
|
+
"es6": true,
|
|
17
|
+
"jquery": true
|
|
18
|
+
},
|
|
19
|
+
"settings": {
|
|
20
|
+
"react": {
|
|
21
|
+
"pragma": "React",
|
|
22
|
+
"version": "0.14.0"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"plugins": [
|
|
26
|
+
"react"
|
|
27
|
+
],
|
|
28
|
+
"rules": {
|
|
29
|
+
"eqeqeq": [ 2, "smart" ],
|
|
30
|
+
"camelcase": 2,
|
|
31
|
+
"no-alert": 2,
|
|
32
|
+
"no-debugger": 2,
|
|
33
|
+
"no-console": 1,
|
|
34
|
+
"no-caller": 2,
|
|
35
|
+
"no-else-return": 1,
|
|
36
|
+
"no-eval": 2,
|
|
37
|
+
"no-extend-native": 1,
|
|
38
|
+
"no-useless-call": 1,
|
|
39
|
+
"no-case-declarations": 0,
|
|
40
|
+
"max-params": [ 2, 3 ],
|
|
41
|
+
"max-nested-callbacks": [ 2, 3 ],
|
|
42
|
+
"new-parens": 2,
|
|
43
|
+
"no-mixed-spaces-and-tabs": 2,
|
|
44
|
+
"operator-assignment": [ 2, "always" ],
|
|
45
|
+
"arrow-parens": [ 1, "always" ],
|
|
46
|
+
"react/no-danger": 1,
|
|
47
|
+
"react/prop-types": 1
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/* eslint-disable camelcase */
|
|
2
|
+
/**
|
|
3
|
+
* This is the default, base configuration object into which grunt-jade merges
|
|
4
|
+
* additional configuration objects from Jade, engine gems, and the current site.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const modulesDir = path.resolve(require.resolve('lodash'), '..', '..');
|
|
11
|
+
const Resolver = require('@epublishing/jade-resolver');
|
|
12
|
+
|
|
13
|
+
module.exports = {
|
|
14
|
+
Resolver,
|
|
15
|
+
paths: {
|
|
16
|
+
app: 'app',
|
|
17
|
+
css: 'public/stylesheets',
|
|
18
|
+
js: 'public/javascripts',
|
|
19
|
+
js_src: 'app/js',
|
|
20
|
+
scss: 'app/sass',
|
|
21
|
+
spec: 'spec/javascripts',
|
|
22
|
+
},
|
|
23
|
+
babel: {
|
|
24
|
+
options: {
|
|
25
|
+
sourceMap: false,
|
|
26
|
+
presets: [
|
|
27
|
+
[require.resolve('@epublishing/babel-preset-epublishing'), {
|
|
28
|
+
transformRuntime: false,
|
|
29
|
+
lodash: { cwd: modulesDir },
|
|
30
|
+
}],
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
compilerOptions: {
|
|
3
|
+
allowJs: true,
|
|
4
|
+
experimentalDecorators: true,
|
|
5
|
+
allowSyntheticDefaultImports: true,
|
|
6
|
+
baseUrl: ".",
|
|
7
|
+
downlevelIteration: true,
|
|
8
|
+
forceConsistentCasingInFileNames: true,
|
|
9
|
+
lib: ["dom", "es6"],
|
|
10
|
+
module: "esnext",
|
|
11
|
+
moduleResolution: "node",
|
|
12
|
+
target: "es6",
|
|
13
|
+
},
|
|
14
|
+
};
|
package/lib/cli-flags.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const cliui = require('cliui');
|
|
5
|
+
|
|
6
|
+
const flags = {
|
|
7
|
+
debug: 'Pretty-print a representation of the complete Grunt config object',
|
|
8
|
+
lint: 'Check the syntax of Webpack bundle JS files w/ eslint',
|
|
9
|
+
'no-minify': 'Skip minification of Webpack bundles',
|
|
10
|
+
'no-banner': 'Suppress ASCII banner',
|
|
11
|
+
'no-time': 'Disable time-grunt performance stats',
|
|
12
|
+
analyze: 'Output Webpack bundle stats JSON and a graphical analysis of all bundles',
|
|
13
|
+
verbose: 'Show (much) more output',
|
|
14
|
+
watch: 'Keep Webpack alive and re-bundle when files change',
|
|
15
|
+
env: 'Sets process.env.NODE_ENV to the specified value',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const names = Object.keys(flags);
|
|
19
|
+
|
|
20
|
+
module.exports = function cliFlags() {
|
|
21
|
+
const ui = cliui({ width: 100 });
|
|
22
|
+
ui.div({ text: chalk.cyan.bold('Option Flags:'), padding: [ 1, 0, 1, 0 ] });
|
|
23
|
+
|
|
24
|
+
for (const name of names) {
|
|
25
|
+
ui.div(
|
|
26
|
+
{
|
|
27
|
+
text: chalk.bold(`--${name}`),
|
|
28
|
+
width: 20,
|
|
29
|
+
padding: [ 0, 4, 0, 4 ],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
text: flags[name],
|
|
33
|
+
width: 80,
|
|
34
|
+
padding: [ 0, 4, 0, 4 ],
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return ui.toString();
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
module.exports.flags = flags;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const cssVariables = require('postcss-css-variables');
|
|
4
|
+
|
|
5
|
+
module.exports = function configurePostCSS(config, grunt) {
|
|
6
|
+
config.postcss.options = {
|
|
7
|
+
map: {
|
|
8
|
+
inline: false,
|
|
9
|
+
},
|
|
10
|
+
processors: [
|
|
11
|
+
cssVariables({
|
|
12
|
+
preserve: true,
|
|
13
|
+
}),
|
|
14
|
+
],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
return config;
|
|
18
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This function merges custom SassScript functions into the base Sass config
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const _ = require('lodash');
|
|
8
|
+
const sass = require('node-sass');
|
|
9
|
+
const NODE_ENV = process.env.NODE_ENV || 'development';
|
|
10
|
+
|
|
11
|
+
module.exports = function configureSass(config) {
|
|
12
|
+
|
|
13
|
+
config.sass.options.functions = {
|
|
14
|
+
'epub-show-deprecation-warnings()': () => {
|
|
15
|
+
if (_.includes([ 'local', 'development' ], NODE_ENV)) {
|
|
16
|
+
return sass.types.Boolean.TRUE;
|
|
17
|
+
}
|
|
18
|
+
return sass.types.Boolean.FALSE;
|
|
19
|
+
},
|
|
20
|
+
'epub-node-env()': () => new sass.types.String(NODE_ENV),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return config;
|
|
24
|
+
};
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This function establishes base configuration for all detected Webpack build
|
|
3
|
+
* targets, including loaders and output plugins.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const os = require('os');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const webpack = require('webpack');
|
|
12
|
+
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
|
13
|
+
const CompressionPlugin = require('compression-webpack-plugin');
|
|
14
|
+
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
|
15
|
+
const lodashDir = path.dirname(require.resolve('lodash'));
|
|
16
|
+
const moduleDir = path.dirname(lodashDir);
|
|
17
|
+
|
|
18
|
+
module.exports = function configureWebpack(grunt, config) {
|
|
19
|
+
const { NODE_ENV = 'development' } = process.env;
|
|
20
|
+
const watch = !!grunt.option('watch');
|
|
21
|
+
const verbose = !!grunt.option('verbose');
|
|
22
|
+
const noMinify = !!grunt.option('no-minify');
|
|
23
|
+
const analyze = !!grunt.option('analyze');
|
|
24
|
+
const lint = NODE_ENV === 'test' || !!grunt.option('lint');
|
|
25
|
+
|
|
26
|
+
for (const target in config.webpack) {
|
|
27
|
+
const targetConfig = config.webpack[target];
|
|
28
|
+
|
|
29
|
+
// This makes select environment variables from the shell running Grunt available
|
|
30
|
+
// in client-side JS files (with values hard-coded at compile-time). The property values
|
|
31
|
+
// in the EnvironmentPlugin's configuration object function as default values, which
|
|
32
|
+
// will be used if no variable corresponding to the property name can be found
|
|
33
|
+
// in the user's terminal environment.
|
|
34
|
+
//
|
|
35
|
+
// They are accessible from bundled scripts the same way that environment variables in Node.js
|
|
36
|
+
// scripts are - as properties of the `process.env` object (i.e. process.env.NODE_ENV).
|
|
37
|
+
const environmentVars = new webpack.EnvironmentPlugin({
|
|
38
|
+
NODE_ENV,
|
|
39
|
+
DEBUG: false,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// This prevents Webpack from loading every single locale definition file that Moment.js provides.
|
|
43
|
+
// 99% of the time we only care about formatting dates in US English, so we have no need for i.e.
|
|
44
|
+
// the preferred date formats of Esperanto-speaking residents of Papua New Guinea. American cultural hedgemony FTW!
|
|
45
|
+
const momentLocales = new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /en/);
|
|
46
|
+
|
|
47
|
+
let plugins = [ environmentVars, momentLocales ];
|
|
48
|
+
|
|
49
|
+
if (!config.babelLoader) {
|
|
50
|
+
config.babelLoader = { exceptions: [], exclude: /(node_modules|bower_components)/ };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const babelExclude = config.babelLoader.exclude;
|
|
54
|
+
const babelExceptions = config.babelLoader.exceptions.map((mod) => new RegExp(`node_modules/${mod}/(.+)\\.js$`));
|
|
55
|
+
|
|
56
|
+
targetConfig.watch = watch;
|
|
57
|
+
targetConfig.keepalive = watch || analyze;
|
|
58
|
+
targetConfig.stats.modules = verbose;
|
|
59
|
+
targetConfig.stats.reasons = verbose;
|
|
60
|
+
targetConfig.profile = analyze;
|
|
61
|
+
|
|
62
|
+
// Tell babel-plugin-lodash where to find modularized Lo-Dash functions:
|
|
63
|
+
targetConfig.resolve.alias.lodash = lodashDir;
|
|
64
|
+
|
|
65
|
+
const babelOptions = {
|
|
66
|
+
presets: [
|
|
67
|
+
[require.resolve('@epublishing/babel-preset-epublishing'), {
|
|
68
|
+
lodash: { cwd: moduleDir },
|
|
69
|
+
env: {
|
|
70
|
+
modules: false,
|
|
71
|
+
},
|
|
72
|
+
minify: false,
|
|
73
|
+
}],
|
|
74
|
+
],
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const rules = [
|
|
78
|
+
{
|
|
79
|
+
test: /\.jsx?$/,
|
|
80
|
+
loader: 'babel-loader',
|
|
81
|
+
exclude: (input) => {
|
|
82
|
+
// Check whether the asset has a matching exclusion exception pattern and allow it to transpile if it does:
|
|
83
|
+
const isException = babelExceptions.some((pattern) => pattern.test(input));
|
|
84
|
+
if (isException) return !isException;
|
|
85
|
+
|
|
86
|
+
// Test asset against the default exclusion pattern and return result:
|
|
87
|
+
return babelExclude.test(input);
|
|
88
|
+
},
|
|
89
|
+
options: babelOptions,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
test: /\.tsx?$/,
|
|
93
|
+
exclude: /node_modules/,
|
|
94
|
+
use: [
|
|
95
|
+
{
|
|
96
|
+
loader: 'babel-loader',
|
|
97
|
+
options: babelOptions,
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
loader: 'ts-loader',
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
test: /\.css$/,
|
|
106
|
+
use: [
|
|
107
|
+
'style-loader',
|
|
108
|
+
'css-loader',
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
test: [ /\.svg$/, /\.jpe?g$/, /\.gif$/, /\.png$/ ],
|
|
113
|
+
loader: 'file-loader',
|
|
114
|
+
},
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
if (lint) {
|
|
118
|
+
const localEslintConfig = path.join(process.cwd(), '.eslintrc');
|
|
119
|
+
const globalEslintConfig = path.resolve(__dirname, '../.eslintrc');
|
|
120
|
+
const eslintConfig = fs.existsSync(localEslintConfig) ? localEslintConfig : globalEslintConfig;
|
|
121
|
+
|
|
122
|
+
rules.push({
|
|
123
|
+
enforce: 'pre',
|
|
124
|
+
test: /\.js$/,
|
|
125
|
+
exclude: /(node_modules|bower_components|public|vendor)/,
|
|
126
|
+
loader: 'eslint-loader',
|
|
127
|
+
options: {
|
|
128
|
+
configFile: eslintConfig,
|
|
129
|
+
formatter: require('eslint-friendly-formatter'),
|
|
130
|
+
failOnError: true,
|
|
131
|
+
outputReport: {
|
|
132
|
+
filePath: 'eslint.xml',
|
|
133
|
+
formatter: require('eslint/lib/formatters/junit'),
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
targetConfig.module = { rules };
|
|
140
|
+
|
|
141
|
+
if (analyze) {
|
|
142
|
+
const bundleAnalyzer = new BundleAnalyzerPlugin({
|
|
143
|
+
analyzerMode: 'server',
|
|
144
|
+
analyzerHost: '127.0.0.1',
|
|
145
|
+
analyzerPort: '8888',
|
|
146
|
+
reportFilename: 'webpack-analysis.html',
|
|
147
|
+
defaultSizes: 'parsed',
|
|
148
|
+
openAnalyzer: true,
|
|
149
|
+
generateStatsFile: true,
|
|
150
|
+
statsFilename: 'webpack.stats.json',
|
|
151
|
+
statsOptions: { chunkModules: true },
|
|
152
|
+
});
|
|
153
|
+
plugins.push(bundleAnalyzer);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!noMinify) {
|
|
157
|
+
const uglifyDefaults = {
|
|
158
|
+
cache: true,
|
|
159
|
+
sourceMap: true,
|
|
160
|
+
parallel: Math.max(os.cpus().length / 2, 1),
|
|
161
|
+
};
|
|
162
|
+
const { uglifyConfig = {} } = targetConfig;
|
|
163
|
+
delete targetConfig.uglifyConfig;
|
|
164
|
+
|
|
165
|
+
plugins.push(new UglifyJsPlugin(Object.assign({}, uglifyDefaults, uglifyConfig)));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (NODE_ENV === 'production') {
|
|
169
|
+
plugins.push(new CompressionPlugin({
|
|
170
|
+
asset: '[path].gz',
|
|
171
|
+
algorithm: 'gzip',
|
|
172
|
+
test: /\.js$/,
|
|
173
|
+
threshold: 10240,
|
|
174
|
+
minRatio: 0.8,
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (Array.isArray(targetConfig.appendPlugins)) {
|
|
179
|
+
plugins = plugins.concat(targetConfig.appendPlugins);
|
|
180
|
+
delete targetConfig.appendPlugins;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
targetConfig.plugins = plugins;
|
|
184
|
+
|
|
185
|
+
if (targetConfig.customize && typeof targetConfig.customize === 'function') {
|
|
186
|
+
config.webpack[target] = targetConfig.customize(targetConfig, webpack);
|
|
187
|
+
delete config.webpack[target].customize;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return config;
|
|
192
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const getGemPaths = require('@epublishing/get-gem-paths');
|
|
2
|
+
const flatten = require('lodash/flatten');
|
|
3
|
+
const merge = require('lodash/merge');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const baseConfig = require('./base-tsconfig');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generate an object representing all of the tsconfig.json files needed to build a site
|
|
9
|
+
* @param {string} sitePath - absolute path to site
|
|
10
|
+
*
|
|
11
|
+
* @typedef {Object} TsconfigData
|
|
12
|
+
* @property {string} location - the absolute path of the tsconfig
|
|
13
|
+
* @property {Object} tsconfig - the tsconfig object to write to file
|
|
14
|
+
*
|
|
15
|
+
* @returns TsconfigData[]
|
|
16
|
+
*/
|
|
17
|
+
module.exports = function genTsConfig(sitePath = process.cwd()) {
|
|
18
|
+
return getGemPaths('jade_?', sitePath)
|
|
19
|
+
.then(function(gems) {
|
|
20
|
+
const sourceRoots = gems
|
|
21
|
+
.concat() // don't mutate input param
|
|
22
|
+
.sort(function(a, _b) {
|
|
23
|
+
// jade should always come last
|
|
24
|
+
if (a.name === 'jade') return 1
|
|
25
|
+
return -1;
|
|
26
|
+
})
|
|
27
|
+
.map(function(gem) { return gem.path })
|
|
28
|
+
|
|
29
|
+
sourceRoots.unshift(sitePath) // site should always come first
|
|
30
|
+
|
|
31
|
+
const assetLocation = 'app/js/*' // only assets in app/js should be considered.
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* for each source root get the relative path from the root to all roots
|
|
35
|
+
* (including self) + each asset location
|
|
36
|
+
*/
|
|
37
|
+
return sourceRoots.map((sourceRoot) => {
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* generate all of the relative paths between a given source
|
|
41
|
+
* (site, jade engine or jade child) and all other sources
|
|
42
|
+
*/
|
|
43
|
+
const relativePaths = sourceRoots.map((root) => {
|
|
44
|
+
const destination = path.resolve(root, assetLocation);
|
|
45
|
+
return path.relative(sourceRoot, destination);
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const pathsField = {
|
|
49
|
+
compilerOptions: {
|
|
50
|
+
paths: {
|
|
51
|
+
'*': flatten(relativePaths),
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const tsconfig = merge({}, baseConfig, pathsField)
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
location: sourceRoot,
|
|
60
|
+
tsconfig,
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/* eslint-disable max-params */
|
|
2
|
+
/**
|
|
3
|
+
* Initializer function for creating a complete Grunt configuration object for
|
|
4
|
+
* an ePublishing Jade site. Merges config values from Jade, any detected engine
|
|
5
|
+
* gems, and the site itself.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const _ = require('lodash');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const prettyjson = require('prettyjson');
|
|
13
|
+
const mergeConfigs = require('./merge-configs');
|
|
14
|
+
const configureWebpack = require('./configure-webpack');
|
|
15
|
+
const configureSass = require('./configure-sass');
|
|
16
|
+
const configurePostCSS = require('./configure-postcss');
|
|
17
|
+
|
|
18
|
+
module.exports = function initJadeConfig(grunt, jadePath, jadeChildPath, jadeChildPaths) {
|
|
19
|
+
// Initialize baseConfig
|
|
20
|
+
let baseConfig = require('./base-config');
|
|
21
|
+
|
|
22
|
+
baseConfig.paths.jade = jadePath;
|
|
23
|
+
baseConfig.paths.jadechild = jadeChildPath;
|
|
24
|
+
|
|
25
|
+
// Add in the jadeChildPaths
|
|
26
|
+
for (const i in jadeChildPaths) {
|
|
27
|
+
baseConfig.paths[i] = jadeChildPaths[i];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Merge Jade's Grunt configuration into baseConfig
|
|
31
|
+
baseConfig = mergeConfigs(baseConfig, jadePath);
|
|
32
|
+
|
|
33
|
+
// Loop through all detected engine gem paths and merge their Grunt configurations into baseConfig
|
|
34
|
+
for (const childPath of _.values(jadeChildPaths)) {
|
|
35
|
+
baseConfig = mergeConfigs(baseConfig, childPath);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Finally, merge the site's configuration values into baseConfig
|
|
39
|
+
baseConfig = mergeConfigs(baseConfig, process.cwd());
|
|
40
|
+
|
|
41
|
+
// Update baseConfig's Webpack settings with ePublishing defaults:
|
|
42
|
+
baseConfig = configureWebpack(grunt, baseConfig);
|
|
43
|
+
|
|
44
|
+
// Add custom functions and settings to the merged Sass config
|
|
45
|
+
baseConfig = configureSass(baseConfig);
|
|
46
|
+
|
|
47
|
+
if (baseConfig.postcss) {
|
|
48
|
+
baseConfig = configurePostCSS(baseConfig, grunt);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const jadeTasks = [
|
|
52
|
+
'gen-tsconfig',
|
|
53
|
+
'npm-install',
|
|
54
|
+
'clean',
|
|
55
|
+
'webpack',
|
|
56
|
+
'babel',
|
|
57
|
+
'concat',
|
|
58
|
+
'uglify',
|
|
59
|
+
'sass',
|
|
60
|
+
'clean-tsconfig',
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
if (baseConfig.bless) {
|
|
64
|
+
jadeTasks.push('bless');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (baseConfig.postcss) {
|
|
68
|
+
jadeTasks.push('postcss');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Initialize the grunt configuration
|
|
72
|
+
grunt.config.init(baseConfig);
|
|
73
|
+
|
|
74
|
+
// Add package.json to the grunt config
|
|
75
|
+
grunt.config.set('pkg', grunt.file.readJSON('package.json'));
|
|
76
|
+
|
|
77
|
+
// Check to see if we need to add the bower tasks
|
|
78
|
+
if (fs.existsSync('bower.json')) {
|
|
79
|
+
jadeTasks.unshift('bower-install-simple', 'bower');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Register the main jade task!
|
|
83
|
+
grunt.registerTask('jade', jadeTasks);
|
|
84
|
+
|
|
85
|
+
// This is left for legacy sake. The task should be called 'jade' because that's
|
|
86
|
+
// what this module is called, but some people are already using this.
|
|
87
|
+
grunt.registerTask('jade-default', jadeTasks);
|
|
88
|
+
|
|
89
|
+
// Optionally output the entire, merged config object via the --debug flag
|
|
90
|
+
grunt.log.debug(prettyjson.render(grunt.config.data));
|
|
91
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const _ = require('lodash');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* splits a path and returns the filename (everything after the last /)
|
|
11
|
+
* @param {string} fullPath - A path to a file
|
|
12
|
+
* @returns {string} the extracted filename
|
|
13
|
+
*/
|
|
14
|
+
function getFilename(fullPath) {
|
|
15
|
+
if (!fullPath) {
|
|
16
|
+
console.trace();
|
|
17
|
+
throw new Error('fullPath was undefined');
|
|
18
|
+
}
|
|
19
|
+
return path.basename(fullPath);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* This function merges grunt configuration objects.
|
|
24
|
+
* This is a function fed into the lodash libraries
|
|
25
|
+
* _.merge function. It only handles merging arrays.
|
|
26
|
+
* The default is to just concat the dest array onto
|
|
27
|
+
* the end of the src array.
|
|
28
|
+
*
|
|
29
|
+
* However if the elements in the array contain references to .js files
|
|
30
|
+
* it instead checks the file name of the .js file (everything after the last /)
|
|
31
|
+
* and OVERRIDES the .js file from the source if it exists in the dest
|
|
32
|
+
*
|
|
33
|
+
* Any src js files that are not in the dest are simply added to the end of the dest
|
|
34
|
+
*
|
|
35
|
+
* @param {Object} dest The destination object to be merged
|
|
36
|
+
* @param {Object} src The source object to be merged into the dest
|
|
37
|
+
* @returns {Object} Either the dest object or undefined to let lodash handle it.
|
|
38
|
+
*/
|
|
39
|
+
function configMerger(dest, src) {
|
|
40
|
+
// We only trigger the custom merge for arrays
|
|
41
|
+
if (!_.isArray(dest)) return;
|
|
42
|
+
|
|
43
|
+
const jsRegExp = /[^*]\.js$/;
|
|
44
|
+
|
|
45
|
+
let i, j;
|
|
46
|
+
|
|
47
|
+
// JavaScript files with the same base name override one another:
|
|
48
|
+
for (i = 0; i < dest.length; i++) {
|
|
49
|
+
const destFile = getFilename(dest[i]);
|
|
50
|
+
|
|
51
|
+
if (!jsRegExp.test(destFile)) continue;
|
|
52
|
+
|
|
53
|
+
for (j = 0; j < src.length; j++) {
|
|
54
|
+
const srcFile = getFilename(src[j]);
|
|
55
|
+
if (destFile !== srcFile) continue;
|
|
56
|
+
dest[i] = src[j];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// All other array members are pushed and de-duped
|
|
61
|
+
for (const member of src) {
|
|
62
|
+
if (_.includes(dest, member)) continue;
|
|
63
|
+
dest.push(member);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return dest;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Merge a base Grunt configuration object with the configuration found
|
|
71
|
+
* in the specified base path, if any.
|
|
72
|
+
*
|
|
73
|
+
* @param {Object} baseConfig - The base Grunt configuration object
|
|
74
|
+
* @param {string} configDir - The root path of a site or Rails engine that contains additional configuration to be merged in
|
|
75
|
+
* @returns {Object} A new configuration object
|
|
76
|
+
*/
|
|
77
|
+
module.exports = function mergeConfigs(baseConfig, configDir) {
|
|
78
|
+
const configFile = path.join(configDir, 'config', 'grunt-config.js');
|
|
79
|
+
|
|
80
|
+
if (!fs.existsSync(configFile)) return baseConfig;
|
|
81
|
+
|
|
82
|
+
let config = require(configFile);
|
|
83
|
+
|
|
84
|
+
if (_.isFunction(config)) {
|
|
85
|
+
config = config(baseConfig);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return _.mergeWith({}, baseConfig, config, configMerger);
|
|
89
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* eslint-disable class-methods-use-this, no-console */
|
|
2
|
+
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
class WebpackConsoleTimer {
|
|
6
|
+
apply(compiler) {
|
|
7
|
+
compiler.plugin('compilation', (compilation) => {
|
|
8
|
+
let startOptimizePhase;
|
|
9
|
+
|
|
10
|
+
compilation.plugin('optimize-chunk-assets', (chunks, callback) => {
|
|
11
|
+
startOptimizePhase = Date.now();
|
|
12
|
+
callback();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
compilation.plugin('after-optimize-chunk-assets', () => {
|
|
16
|
+
const optimizePhaseDuration = Date.now() - startOptimizePhase;
|
|
17
|
+
console.log(`\nOptimizer phase duration: ${chalk.bold(optimizePhaseDuration)}ms`);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = WebpackConsoleTimer;
|
package/package.json
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@epublishing/grunt-epublishing",
|
|
3
|
+
"description": "Automated front-end tasks for ePublishing Jade and client sites.",
|
|
4
|
+
"version": "0.3.0",
|
|
5
|
+
"homepage": "https://www.epublishing.com",
|
|
6
|
+
"contributors": [
|
|
7
|
+
{
|
|
8
|
+
"name": "Nick Brewer",
|
|
9
|
+
"email": "nbrewer@epublishing.com"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"name": "Mike Green",
|
|
13
|
+
"email": "mgreen@epublishing.com"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"repository": "bitbucket:epub_dev/grunt-epublishing",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">= 8.0.0"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@epublishing/babel-preset-epublishing": "^0.1.6",
|
|
23
|
+
"@epublishing/get-gem-paths": "^0.1.1",
|
|
24
|
+
"@epublishing/grunt-install-eslint": "^0.1.1",
|
|
25
|
+
"@epublishing/jade-resolver": "^0.1.2",
|
|
26
|
+
"async": "^2.6.1",
|
|
27
|
+
"babel-loader": "^7.1.5",
|
|
28
|
+
"babel-minify-webpack-plugin": "^0.3.0",
|
|
29
|
+
"bourbon": "^4.2.7",
|
|
30
|
+
"breakpoint-sass": "^2.7.0",
|
|
31
|
+
"chalk": "^2.4.1",
|
|
32
|
+
"cli-spinner": "^0.2.8",
|
|
33
|
+
"cliui": "^4.1.0",
|
|
34
|
+
"compass-mixins": "^0.12.10",
|
|
35
|
+
"compression-webpack-plugin": "^1.1.7",
|
|
36
|
+
"css-loader": "^0.28.9",
|
|
37
|
+
"cssnano": "^4.0.5",
|
|
38
|
+
"es5-shim": "^4.5.10",
|
|
39
|
+
"es6-promise": "^4.2.4",
|
|
40
|
+
"eslint": "^5.3.0",
|
|
41
|
+
"eslint-friendly-formatter": "^4.0.1",
|
|
42
|
+
"eslint-loader": "^2.1.0",
|
|
43
|
+
"eslint-plugin-import": "^2.13.0",
|
|
44
|
+
"eslint-plugin-react": "^7.10.0",
|
|
45
|
+
"execa": "^0.10.0",
|
|
46
|
+
"exports-loader": "^0.7.0",
|
|
47
|
+
"expose-loader": "^0.7.4",
|
|
48
|
+
"file-loader": "^1.1.11",
|
|
49
|
+
"grunt": "^1.0.3",
|
|
50
|
+
"grunt-babel": "^7.0.0",
|
|
51
|
+
"grunt-bless": "^1.0.2",
|
|
52
|
+
"grunt-bower": "^0.21.4",
|
|
53
|
+
"grunt-bower-install-simple": "^1.2.6",
|
|
54
|
+
"grunt-contrib-clean": "^1.1.0",
|
|
55
|
+
"grunt-contrib-concat": "^1.0.1",
|
|
56
|
+
"grunt-contrib-uglify": "^3.4.0",
|
|
57
|
+
"grunt-contrib-watch": "^1.1.0",
|
|
58
|
+
"grunt-postcss": "^0.9.0",
|
|
59
|
+
"grunt-sass": "^2.1.0",
|
|
60
|
+
"grunt-webpack": "^3.1.2",
|
|
61
|
+
"handlebars": "^4.0.11",
|
|
62
|
+
"handlebars-loader": "^1.6.0",
|
|
63
|
+
"imports-loader": "^0.8.0",
|
|
64
|
+
"jit-grunt": "^0.10.0",
|
|
65
|
+
"listr": "^0.14.1",
|
|
66
|
+
"lodash": "^4.17.10",
|
|
67
|
+
"node-sass": "^4.14.1",
|
|
68
|
+
"postcss-css-variables": "^0.9.0",
|
|
69
|
+
"prettyjson": "^1.2.1",
|
|
70
|
+
"read-pkg": "^4.0.1",
|
|
71
|
+
"style-loader": "^0.20.2",
|
|
72
|
+
"susy": "^2.2.14",
|
|
73
|
+
"time-grunt": "^1.4.0",
|
|
74
|
+
"ts-loader": "^3.5.0",
|
|
75
|
+
"uglifyjs-webpack-plugin": "^1.2.7",
|
|
76
|
+
"webpack": "^3.12.0",
|
|
77
|
+
"webpack-bundle-analyzer": "^2.13.1",
|
|
78
|
+
"webpack-dev-server": "^2.11.1",
|
|
79
|
+
"worker-loader": "^2.0.0"
|
|
80
|
+
},
|
|
81
|
+
"keywords": [
|
|
82
|
+
"gruntplugin"
|
|
83
|
+
],
|
|
84
|
+
"devDependencies": {
|
|
85
|
+
"@epublishing/eslint-config-epublishing": "^0.2.0",
|
|
86
|
+
"bumped": "^0.10.10",
|
|
87
|
+
"bumped-terminal": "^0.7.5",
|
|
88
|
+
"jest": "^24.9.0"
|
|
89
|
+
},
|
|
90
|
+
"scripts": {
|
|
91
|
+
"release": "bumped release",
|
|
92
|
+
"test": "jest"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const genTsConfig = require('../../lib/gen-tsconfig');
|
|
2
|
+
|
|
3
|
+
function expectedConfig(sitePath) {
|
|
4
|
+
|
|
5
|
+
return [{
|
|
6
|
+
location: `${sitePath}`,
|
|
7
|
+
tsconfig: {
|
|
8
|
+
compilerOptions: {
|
|
9
|
+
allowSyntheticDefaultImports: true,
|
|
10
|
+
baseUrl: '.',
|
|
11
|
+
lib: ['dom', 'es6'],
|
|
12
|
+
module: 'esnext',
|
|
13
|
+
moduleResolution: 'node',
|
|
14
|
+
paths: {
|
|
15
|
+
'*': [
|
|
16
|
+
'app/js/*',
|
|
17
|
+
'../../path/to/jade_child/app/js/*',
|
|
18
|
+
'../../path/to/jade/app/js/*'
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
target: 'es6'
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}, {
|
|
25
|
+
location: '/path/to/jade_child',
|
|
26
|
+
tsconfig: {
|
|
27
|
+
compilerOptions: {
|
|
28
|
+
allowSyntheticDefaultImports: true,
|
|
29
|
+
baseUrl: '.',
|
|
30
|
+
lib: ['dom', 'es6'],
|
|
31
|
+
module: 'esnext',
|
|
32
|
+
moduleResolution: 'node',
|
|
33
|
+
paths: {
|
|
34
|
+
'*': [
|
|
35
|
+
`../../..${sitePath}/app/js/*`,
|
|
36
|
+
'app/js/*',
|
|
37
|
+
'../jade/app/js/*',
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
'target': 'es6',
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}, {
|
|
44
|
+
location: '/path/to/jade',
|
|
45
|
+
tsconfig: {
|
|
46
|
+
compilerOptions: {
|
|
47
|
+
allowSyntheticDefaultImports: true,
|
|
48
|
+
baseUrl: '.',
|
|
49
|
+
lib: ['dom', 'es6'],
|
|
50
|
+
module: 'esnext',
|
|
51
|
+
moduleResolution: 'node',
|
|
52
|
+
paths: {
|
|
53
|
+
'*': [
|
|
54
|
+
`../../..${sitePath}/app/js/*`,
|
|
55
|
+
'../jade_child/app/js/*',
|
|
56
|
+
'app/js/*'
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
'target': 'es6',
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
test('returns tsconfigs for pwd', function(done) {
|
|
67
|
+
|
|
68
|
+
const processSpy = jest.spyOn(process, 'cwd').mockImplementation(() => '/my/cwd');
|
|
69
|
+
genTsConfig().then(function(result) {
|
|
70
|
+
expect(result).toEqual(expectedConfig('/my/cwd'));
|
|
71
|
+
processSpy.mockClear()
|
|
72
|
+
done()
|
|
73
|
+
})
|
|
74
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
'use strict';
|
|
3
|
+
/**
|
|
4
|
+
* Remove tsconfig from site and jade engines
|
|
5
|
+
*/
|
|
6
|
+
module.exports = function (grunt) {
|
|
7
|
+
grunt.registerTask('clean-tsconfig', 'Remove tsconfig from site and jade engines', function() {
|
|
8
|
+
const genTsConfig = require('../lib/gen-tsconfig');
|
|
9
|
+
const fs = require('fs')
|
|
10
|
+
const path = require('path')
|
|
11
|
+
|
|
12
|
+
const { NODE_ENV } = process.env;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* if the build is not for a deployment (i.e. development), we'll want to keep tsconfigs
|
|
16
|
+
* a better developer experience
|
|
17
|
+
*/
|
|
18
|
+
if ( NODE_ENV !== 'production' && NODE_ENV !== 'staging' ) {
|
|
19
|
+
console.log('Not deploying to production or stage. tsconfig.json will not be removed.');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const done = this.async();
|
|
24
|
+
|
|
25
|
+
genTsConfig(grunt.option('siteRoot'))
|
|
26
|
+
.then((configPayloads) => {
|
|
27
|
+
configPayloads.forEach(function(payload) {
|
|
28
|
+
const configPath = path.resolve(payload.location, 'tsconfig.json');
|
|
29
|
+
fs.unlinkSync(configPath)
|
|
30
|
+
})
|
|
31
|
+
done();
|
|
32
|
+
}).catch((err) => {
|
|
33
|
+
console.log(err)
|
|
34
|
+
done()
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
'use strict';
|
|
3
|
+
/**
|
|
4
|
+
* This task generates a tsconfig.json for the site as well as its jade engines.
|
|
5
|
+
*/
|
|
6
|
+
module.exports = function(grunt) {
|
|
7
|
+
grunt.registerTask('gen-tsconfig', 'Generate .tsconfig.json for site and all Jade engines', function() {
|
|
8
|
+
const genTsConfig = require('../lib/gen-tsconfig');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
|
|
12
|
+
const done = this.async();
|
|
13
|
+
const tsConfigDotJson = 'tsconfig.json'
|
|
14
|
+
|
|
15
|
+
genTsConfig(grunt.option('siteRoot'))
|
|
16
|
+
.then((configPayloads) => {
|
|
17
|
+
|
|
18
|
+
configPayloads.forEach(function(payload) {
|
|
19
|
+
const configPath = path.resolve(payload.location, tsConfigDotJson);
|
|
20
|
+
createTSConfig(configPath, payload.tsconfig);
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
done();
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* creates a .tsconfig.json
|
|
28
|
+
* @param {string} path - path to write to
|
|
29
|
+
* @param {string} data - data to write
|
|
30
|
+
*/
|
|
31
|
+
function createTSConfig(path, data) {
|
|
32
|
+
fs.writeFileSync(path, JSON.stringify(data))
|
|
33
|
+
console.log('created tsconfig.json for', path)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
package/tasks/jade.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const timeGrunt = require('time-grunt');
|
|
7
|
+
const jitGrunt = require('jit-grunt');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const readPkg = require('read-pkg');
|
|
10
|
+
const getGemPaths = require('@epublishing/get-gem-paths');
|
|
11
|
+
const cliFlags = require('../lib/cli-flags');
|
|
12
|
+
const initJadeConfig = require('../lib/init-jade-config');
|
|
13
|
+
|
|
14
|
+
module.exports = function(grunt) {
|
|
15
|
+
grunt.option('siteRoot', process.cwd())
|
|
16
|
+
|
|
17
|
+
if (!grunt.option('no-time')) timeGrunt(grunt);
|
|
18
|
+
jitGrunt(grunt, {
|
|
19
|
+
'install-eslint': '@epublishing/grunt-install-eslint',
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* This registers a grunt task which shells out and uses bundler to
|
|
24
|
+
* determine the paths to the jade gem and any jade child engine gem
|
|
25
|
+
*/
|
|
26
|
+
grunt.registerTask('set-jade-paths', 'Get Jade Gem Paths', function() {
|
|
27
|
+
let jadePath;
|
|
28
|
+
let jadeChildPath;
|
|
29
|
+
const jadeChildPaths = {};
|
|
30
|
+
const isTerminal = Boolean(process.stdout.isTTY);
|
|
31
|
+
const moduleRoot = path.resolve(__dirname, '..');
|
|
32
|
+
const done = this.async();
|
|
33
|
+
const asciiBanner = fs.readFileSync(path.join(moduleRoot, 'etc/banner.txt'), 'utf8');
|
|
34
|
+
const modulePkg = readPkg.sync({ cwd: moduleRoot });
|
|
35
|
+
const sitePkg = readPkg.sync();
|
|
36
|
+
const banner = [
|
|
37
|
+
asciiBanner,
|
|
38
|
+
`grunt-epublishing v${modulePkg.version}`,
|
|
39
|
+
`currently building: ${sitePkg.name} v${sitePkg.version}`,
|
|
40
|
+
cliFlags(),
|
|
41
|
+
].join('\n');
|
|
42
|
+
|
|
43
|
+
if (grunt.option('env')) {
|
|
44
|
+
process.env.NODE_ENV = grunt.option('env');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (isTerminal && !grunt.option('no-banner')) grunt.log.writeln(`\n${banner}`);
|
|
48
|
+
|
|
49
|
+
// Query bundler for Jade-related gems in a subprocess
|
|
50
|
+
getGemPaths('jade_?')
|
|
51
|
+
.then((data) => {
|
|
52
|
+
for (const gem of data) {
|
|
53
|
+
if (gem.name === 'jade') {
|
|
54
|
+
jadePath = gem.path;
|
|
55
|
+
} else if ((/^jade_/).test(gem.name)) {
|
|
56
|
+
jadeChildPaths[gem.name] = gem.path;
|
|
57
|
+
jadeChildPath = gem.path;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
done();
|
|
62
|
+
initJadeConfig(grunt, jadePath, jadeChildPath, jadeChildPaths);
|
|
63
|
+
})
|
|
64
|
+
.catch((err) => {
|
|
65
|
+
grunt.fail.fatal(err);
|
|
66
|
+
done();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Force-run the `set-jade-paths` task
|
|
71
|
+
grunt.task.run(['set-jade-paths']);
|
|
72
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This registers a grunt task that performs an NPM install at each level of
|
|
3
|
+
* hierarchy that has a package.json file present (jade and child gem). Site
|
|
4
|
+
* level NPM installs are still manual.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
module.exports = function(grunt) {
|
|
10
|
+
const _ = require('lodash');
|
|
11
|
+
const async = require('async');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const Resolver = require('@epublishing/jade-resolver');
|
|
14
|
+
|
|
15
|
+
grunt.registerTask('npm-install', 'Install Node module dependencies', function() {
|
|
16
|
+
const Spinner = require('cli-spinner').Spinner;
|
|
17
|
+
const origPath = process.cwd();
|
|
18
|
+
const resolver = new Resolver(grunt.config.get('paths'));
|
|
19
|
+
const isTerminal = Boolean(process.stdout.isTTY);
|
|
20
|
+
|
|
21
|
+
resolver.removePath('site');
|
|
22
|
+
|
|
23
|
+
const packages = resolver.find('package.json');
|
|
24
|
+
|
|
25
|
+
if (_.isEmpty(packages)) {
|
|
26
|
+
grunt.log.ok('No other package.json files found in hierarchy. Skipping npm-install task.');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const done = this.async();
|
|
31
|
+
const installQueue = _.map(packages, (pkg) => {
|
|
32
|
+
const basePath = path.dirname(pkg);
|
|
33
|
+
const basename = path.basename(basePath);
|
|
34
|
+
|
|
35
|
+
return (callback) => {
|
|
36
|
+
const spinner = new Spinner('%s installing...');
|
|
37
|
+
|
|
38
|
+
spinner.setSpinnerString(Spinner.spinners[18]);
|
|
39
|
+
|
|
40
|
+
grunt.log.writeln(`Installing NPM modules in ${basename}`);
|
|
41
|
+
|
|
42
|
+
if (isTerminal) spinner.start();
|
|
43
|
+
|
|
44
|
+
process.chdir(basePath);
|
|
45
|
+
|
|
46
|
+
grunt.util.spawn({
|
|
47
|
+
cmd: 'npm',
|
|
48
|
+
args: [ 'ci' ],
|
|
49
|
+
}, (err, result) => {
|
|
50
|
+
if (err) {
|
|
51
|
+
grunt.fail.warn(err);
|
|
52
|
+
} else {
|
|
53
|
+
if (isTerminal) spinner.stop(true);
|
|
54
|
+
grunt.verbose.writeln(result.stdout);
|
|
55
|
+
grunt.log.ok('Success!');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
callback(err, result);
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
async.series(installQueue, (err, results) => {
|
|
64
|
+
if (err) {
|
|
65
|
+
return done(err);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
grunt.log.ok('All NPM packages installed');
|
|
69
|
+
process.chdir(origPath);
|
|
70
|
+
done(results);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This allows both the standard watch task and the Webpack watcher to
|
|
3
|
+
* run concurrently
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
module.exports = (grunt) => {
|
|
9
|
+
grunt.registerTask('watch-all', 'Watch with Sass and Webpack concurrently', function() {
|
|
10
|
+
const done = this.async();
|
|
11
|
+
const { spawn } = require('child_process');
|
|
12
|
+
const cwd = process.cwd();
|
|
13
|
+
const { env } = process;
|
|
14
|
+
const [ nodeBin, gruntBin, ...argv ] = process.argv;
|
|
15
|
+
const flags = '--no-banner --no-time';
|
|
16
|
+
const tasks = [
|
|
17
|
+
`watch ${flags}`,
|
|
18
|
+
`webpack --watch ${flags}`,
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
grunt.log.ok('Spawning watcher tasks...');
|
|
22
|
+
|
|
23
|
+
const subprocesses = tasks.map((task) => {
|
|
24
|
+
const args = [ gruntBin, ...task.split(' ') ];
|
|
25
|
+
const subprocess = spawn(nodeBin, args, {
|
|
26
|
+
cwd,
|
|
27
|
+
env,
|
|
28
|
+
stdio: 'inherit',
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
subprocess.on('error', (err) => {
|
|
32
|
+
grunt.log.fatal(err);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return subprocess;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const cleanup = (exit = false) => {
|
|
39
|
+
grunt.log.ok('Killing subprocesses...');
|
|
40
|
+
for (const subprocess of subprocesses) {
|
|
41
|
+
subprocess.kill('SIGKILL');
|
|
42
|
+
}
|
|
43
|
+
if (exit) process.exit();
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
process.on('exit', cleanup);
|
|
47
|
+
process.on('SIGINT', () => cleanup(true));
|
|
48
|
+
});
|
|
49
|
+
};
|