Skip to content
This repository has been archived by the owner on Apr 1, 2020. It is now read-only.

Commit

Permalink
Rhys/decouple asset hashing (#920)
Browse files Browse the repository at this point in the history
* import bits and pieces from n-webpack

* demo build works

* all webpack variants now help 'in house '

* Update package.json

* fixed webpack dep

* decouple asset hashing from webpack
  • Loading branch information
wheresrhys committed Apr 24, 2017
1 parent 640f5a6 commit 6e91aac
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 72 deletions.
8 changes: 3 additions & 5 deletions _entry.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// for the purposes of exposing in a shared n-ui bundle
// This will mean webpack can find them in this bundle under n-ui/componentName
module.exports = function (withPreact, exclusions) {
module.exports = function (exclusions) {
const entry = {
// n-ui components
'n-ui': 'window.ftNextUi',
Expand All @@ -23,10 +23,8 @@ module.exports = function (withPreact, exclusions) {
'superstore-sync': 'window.ftNextUi._superstoreSync',
}

if (withPreact) {
entry.react = 'window.ftNextUi._React';
entry['react-dom'] = 'window.ftNextUi._ReactDom';
}
entry.react = 'window.ftNextUi._React';
entry['react-dom'] = 'window.ftNextUi._ReactDom';

if (exclusions) {
exclusions.forEach(exc => delete entry[exc]);
Expand Down
20 changes: 20 additions & 0 deletions build/app/asset-hashes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const path = require('path');
const fs = require('fs');
const crypto = require('crypto');

module.exports = () => {
const hashes = fs.readdirSync(path.join(process.cwd(), 'public'))
.filter(asset => !/(\.map|about\.json|asset-hashes\.json)$/.test(asset))
.map(name => {
const file = fs.readFileSync(path.join(process.cwd(), 'public', name), 'utf8');
const hash = crypto.createHash('sha1').update(file).digest('hex');
const hashedName = `${hash.substring(0, 8)}/${name}`;
return { name, hashedName };
})
.reduce((previous, current) => {
previous[current.name] = current.hashedName;
previous[current.name + '.map'] = current.hashedName + '.map';
return previous;
}, {});
fs.writeFileSync('./public/asset-hashes.json', JSON.stringify(hashes, null, 2), { encoding: 'UTF8' });
}
2 changes: 2 additions & 0 deletions build/app/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const path = require('path');
const shellpromise = require('shellpromise');
const shellpipe = require('./shellpipe');
const downloadAssets = require('./download-assets');
const assetHashes = require('./asset-hashes')

const exit = err => {
logger.error(err);
Expand Down Expand Up @@ -50,6 +51,7 @@ program
devAdvice();

shellpipe(`webpack ${options.production ? '--bail' : '--dev'} --config ${webpackConfPath}`)
.then(assetHashes)
.then(aboutJson)
.then(downloadAssets)
.then(() => {
Expand Down
124 changes: 75 additions & 49 deletions build/app/webpack.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
const path = require('path');
const nWebpack = require('@financial-times/n-webpack');
const fs = require('fs');
const join = require('path').join;
const Wrap = require('../lib/addons/wrap');
const headCss = require('../lib/head-css')

const gitignore = fs.readFileSync(join(process.cwd(), '.gitignore'), 'utf8')
.split('\n');


function noGitignoreWildcard () {
gitignore.forEach(pattern => {
if (/^\/?public\/(.*\/\*|\*|$)/.test(pattern)) {
if (pattern !== '/public/n-ui/') {
throw new Error('Wildcard pattern or entire directories (i.e. /public/) for built public assets not allowed in your .gitignore. Please specify a path for each file');
}
}
});
}

function clone (obj) {
return JSON.parse(JSON.stringify(obj));
Expand All @@ -22,23 +39,39 @@ function filterEntryKeys (obj, rx, negativeMatch) {
}, {})
}

function constructVariants (nWebpackOptions) {

// we no longer build a main.js for the app when generating the standard asset variants
const variants = [
Object.assign({}, nWebpackOptions, {
entry: filterEntryKeys(nWebpackOptions.entry, /main\.js$/, true)
})
]

variants.push(Object.assign(clone(nWebpackOptions), {
language: 'js',
externals: {'n-ui': true},
entry: modifyEntryKeys(nWebpackOptions.entry, /main\.js$/, name => name.replace(/\.js$/,'-without-n-ui.js'))
}))

if (process.env.NEXT_APP_SHELL === 'local') {
const nWebpackWarning = `
const baseConfig = require(path.join(process.cwd(), 'n-ui-build.config.js'));

noGitignoreWildcard();

// we no longer build a main.js for the app when generating the standard asset variants
const variants = [
// all entry points excluding main.js generated as normal
headCss(nWebpack(Object.assign({}, baseConfig, {
entry: filterEntryKeys(baseConfig.entry, /main\.js$/, true)
})))
]

// new entry point for main.js declaring external n-ui
const mainJs = nWebpack(Object.assign(clone(baseConfig), {
language: 'js',
entry: modifyEntryKeys(baseConfig.entry, /main\.js$/, name => name.replace(/\.js$/,'-without-n-ui.js'))
}))

const nUiEntry = path.join(process.cwd(), 'bower_components/n-ui/_entry');
const nUiEntryPoints = require(nUiEntry)(baseConfig.nUiExcludes)
mainJs.externals = Object.assign({}, mainJs.externals, nUiEntryPoints);
mainJs.plugins.push(
new Wrap(
'(function(){function init(){\n',
'\n};window.ftNextnUiLoaded ? init() : document.addEventListener ? document.addEventListener(\'ftNextnUiLoaded\', init) : document.attachEvent(\'onftNextnUiLoaded\', init);})();',
{ match: /\.js$/ }
)
);

variants.push(mainJs);

if (process.env.NEXT_APP_SHELL === 'local') {
const nWebpackWarning = `
/*********** n-webpack warning ************/
You have set the environment variable NEXT_APP_SHELL=local
Expand All @@ -48,43 +81,36 @@ or similar). It will slow down your build A LOT!!!!
If you do not need this behaviour run
unset NEXT_APP_SHELL
unset NEXT_APP_SHELL
/*********** n-webpack warning ************/
`;
console.warn(nWebpackWarning); // eslint-disable-line no-console
console.warn(nWebpackWarning); // eslint-disable-line no-console

const ignoresNUi = fs.readFileSync(path.join(process.cwd(), '.gitignore'), 'utf8')
.split('\n')
.some(line => line === '/public/n-ui/');

if (!ignoresNUi) {
throw 'Add /public/n-ui/ to your .gitignore to start building a local app shell';
}
const ignoresNUi = fs.readFileSync(path.join(process.cwd(), '.gitignore'), 'utf8')
.split('\n')
.some(line => line === '/public/n-ui/');

const appShellBuild = Object.assign(clone(nWebpackOptions), {
language: 'js',
env: 'dev',
withBabelPolyfills: false,
output: {
filename: '[name]',
library: 'ftNextUi',
devtoolModuleFilenameTemplate: 'n-ui//[resource-path]?[loaders]'
},
entry: {},
exclude: [/node_modules/]
});

appShellBuild.entry['./public/n-ui/es5.js'] = './bower_components/n-ui/build/deploy/wrapper.js'
variants.push(appShellBuild);
if (!ignoresNUi) {
throw 'Add /public/n-ui/ to your .gitignore to start building a local app shell';
}
// can't just variants.map(nWebpack) becaue second param truthiness
return variants.map(conf => nWebpack(conf))
}

const baseConfig = Object.assign({}, {
withHeadCss: true,
withHashedAssets: true
}, require(path.join(process.cwd(), 'n-ui-build.config.js')));
const appShellBuild = Object.assign(clone(baseConfig), {
language: 'js',
env: 'dev',
withBabelPolyfills: false,
output: {
filename: '[name]',
library: 'ftNextUi',
devtoolModuleFilenameTemplate: 'n-ui//[resource-path]?[loaders]'
},
entry: {
'./public/n-ui/es5.js': './bower_components/n-ui/build/deploy/wrapper.js'
},
exclude: [/node_modules/]
});

variants.push(nWebpack(appShellBuild));
}

module.exports = constructVariants(baseConfig);
module.exports = variants
36 changes: 22 additions & 14 deletions build/deploy/webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';
const nWebpack = require('@financial-times/n-webpack');
const headCss = require('../lib/head-css')

const coreConfig = {
output: {
Expand All @@ -11,9 +12,7 @@ const coreConfig = {
exclude: [/node_modules/]
};

// Build variants of the bundle that work with different combinations of feature flags
// Only build some of them when bower linking in dev to save build time
module.exports = [
const configs = [
{
withBabelPolyfills: false,
env: 'dev',
Expand All @@ -22,23 +21,32 @@ module.exports = [
},
buildInDev: true
},
{
withBabelPolyfills: false,
env: 'prod',
entry: {
'./dist/assets/es5.min.js': './build/deploy/wrapper.js'
}
},
{
withBabelPolyfills: false,
env: 'prod',
entry: {
'./dist/assets/n-ui-core.css': './build/deploy/shared-head.scss'
},
withHeadCss: true,
wrap: undefined,
buildInDev: true
}
]
.filter(conf => conf.buildInDev || !process.env.DEV_BUILD)
.map(conf => nWebpack(Object.assign({}, coreConfig, conf)));
];

if (!process.env.DEV_BUILD) {
configs.push({
withBabelPolyfills: false,
env: 'prod',
entry: {
'./dist/assets/es5.min.js': './build/deploy/wrapper.js'
}
})
}

module.exports = configs
.map(conf => {
const webpackConf = nWebpack(Object.assign({}, coreConfig, conf));
if (conf.withHeadCss) {
return headCss(webpackConf)
}
return webpackConf
})
29 changes: 29 additions & 0 deletions build/lib/addons/wrap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Slightly modified version of entry-wrap-webpack-plugin https://github.com/shakyShane/entry-wrap-webpack-plugin
const ConcatSource = require('webpack/lib/ConcatSource');

function Wrap (before, after, options) {
this.options = options || {};
this.before = before;
this.after = after;
}

Wrap.prototype.apply = function (compiler) {
const options = this.options;
const before = this.before;
const after = this.after;

compiler.plugin('compilation', function (compilation) {
compilation.plugin('optimize-chunk-assets', function (chunks, callback) {
chunks.forEach(function (chunk) {
if(!chunk.initial) return;
const files = chunk.files.filter(file => options.match ? options.match.test(file) : true);
files.forEach(function (file) {
compilation.assets[file] = new ConcatSource(before, '\n', compilation.assets[file], '\n', after);
});
});
callback();
});
});
};

module.exports = Wrap;
6 changes: 6 additions & 0 deletions build/lib/head-css.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const ExtractCssBlockPlugin = require('extract-css-block-webpack-plugin');

module.exports = function (config) {
config.plugins.push(new ExtractCssBlockPlugin());
return config;
}
8 changes: 5 additions & 3 deletions demo/webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
'use strict';

const nWebpack = require('@financial-times/n-webpack');
const headCss = require('../build/lib/head-css')
const path = require('path');
module.exports = nWebpack({
const webpackConfig = headCss(nWebpack({
withBabelPolyfills: false,
withHeadCss: true,
entry: {
'./public/main-without-n-ui.js': './demo/client/main.js',
'./public/main.css': './demo/client/main.scss'
Expand All @@ -13,4 +13,6 @@ module.exports = nWebpack({
path.join(__dirname, '../')
],
exclude: [/node_modules/]
});
}));

module.exports = webpackConfig;
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@
"uglifyjs": "^2.4.10"
},
"dependencies": {
"@financial-times/n-webpack": "^3.0.0",
"@financial-times/n-express": "^19.0.0",
"@financial-times/n-handlebars": "^1.15.0",
"@financial-times/n-polyfill-io": "^1.0.9",
"@financial-times/n-webpack": "^2.0.0",
"@financial-times/next-json-ld": "^0.1.0",
"aws-sdk": "^2.7.21",
"chokidar": "^1.6.1",
Expand Down

0 comments on commit 6e91aac

Please sign in to comment.