Commit dd2f2eb0 authored by Nick Stenning's avatar Nick Stenning

Merge pull request #2958 from hypothesis/gulp-frontend-build

New front-end build system based on Gulp
parents dd2b7aed 7fca206a
......@@ -26,18 +26,18 @@
"phantom": true,
"jquery": true,
"predef": [
"-Promise", // We currently support IE10-11 which lack a Promise global
"angular",
"assert",
"Promise",
"URL",
"after",
"afterEach",
"angular",
"assert",
"before",
"beforeEach",
"context",
"describe",
"it",
"require",
"sinon",
"URL"
"sinon"
]
}
'use strict';
require('core-js/es6/promise');
require('core-js/fn/object/assign');
require('core-js/fn/string');
var batch = require('gulp-batch');
var changed = require('gulp-changed');
var endOfStream = require('end-of-stream');
var gulp = require('gulp');
var gulpIf = require('gulp-if');
var gulpUtil = require('gulp-util');
var sass = require('gulp-sass');
var postcss = require('gulp-postcss');
var runSequence = require('run-sequence');
var sourcemaps = require('gulp-sourcemaps');
var manifest = require('./scripts/gulp/manifest');
var createBundle = require('./scripts/gulp/create-bundle');
var vendorBundles = require('./scripts/gulp/vendor-bundles');
var IS_PRODUCTION_BUILD = process.env.NODE_ENV === 'production';
var SCRIPT_DIR = 'build/scripts';
var STYLE_DIR = 'build/styles';
var FONTS_DIR = 'build/fonts';
var IMAGES_DIR = 'build/images';
function isSASSFile(file) {
return file.path.match(/\.scss$/);
}
/** A list of all modules included in vendor bundles. */
var vendorModules = Object.keys(vendorBundles.bundles)
.reduce(function (deps, key) {
return deps.concat(vendorBundles.bundles[key]);
}, []);
// Builds the bundles containing vendor JS code
gulp.task('build-vendor-js', function () {
var finished = [];
Object.keys(vendorBundles.bundles).forEach(function (name) {
finished.push(createBundle({
name: name,
require: vendorBundles.bundles[name],
minify: IS_PRODUCTION_BUILD,
path: SCRIPT_DIR,
noParse: vendorBundles.noParseModules,
}));
});
return Promise.all(finished);
});
var appBundleBaseConfig = {
path: SCRIPT_DIR,
external: vendorModules,
minify: IS_PRODUCTION_BUILD,
noParse: vendorBundles.noParseModules,
};
var appBundles = [{
// The sidebar application for displaying and editing annotations
name: 'app',
transforms: ['coffee'],
entry: './h/static/scripts/app.coffee',
},{
// Browser extension background script
name: 'extension',
entry: './h/browser/chrome/lib/extension',
},{
// The Annotator library which provides annotation controls on
// the page and sets up the sidebar
name: 'injector',
entry: './h/static/scripts/annotator/main',
transforms: ['coffee'],
},{
// Public-facing website
name: 'site',
entry: './h/static/scripts/site',
},{
// Admin areas of the site
name: 'admin-site',
entry: './h/static/scripts/admin-site',
}];
var appBundleConfigs = appBundles.map(function (config) {
return Object.assign({}, appBundleBaseConfig, config);
});
gulp.task('build-app-js', ['build-vendor-js'], function () {
return Promise.all(appBundleConfigs.map(function (config) {
return createBundle(config);
}));
});
gulp.task('watch-app-js', ['build-vendor-js'], function () {
appBundleConfigs.map(function (config) {
createBundle(config, {watch: true});
});
});
var styleFiles = [
// H
'./h/static/styles/admin.scss',
'./h/static/styles/annotator/inject.scss',
'./h/static/styles/annotator/pdfjs-overrides.scss',
'./h/static/styles/app.scss',
'./h/static/styles/front-page/main.css',
'./h/static/styles/help-page.scss',
'./h/static/styles/site.scss',
// Vendor
'./h/static/styles/vendor/angular-csp.css',
'./h/static/styles/vendor/icomoon.css',
'./h/static/styles/vendor/katex.min.css',
'./node_modules/angular-toastr/dist/angular-toastr.css',
'./node_modules/bootstrap/dist/css/bootstrap.css',
];
gulp.task('build-css', function () {
var sassOpts = {
outputStyle: IS_PRODUCTION_BUILD ? 'compressed' : 'nested',
includePaths: ['node_modules/compass-mixins/lib/'],
};
return gulp.src(styleFiles)
.pipe(sourcemaps.init())
.pipe(gulpIf(isSASSFile, sass(sassOpts).on('error', sass.logError)))
.pipe(postcss([require('autoprefixer')]))
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest(STYLE_DIR));
});
gulp.task('watch-css', function () {
gulp.watch('./h/static/styles/**/*.scss', ['build-css']);
});
var fontFiles = 'h/static/styles/vendor/fonts/*.woff';
gulp.task('build-fonts', function () {
gulp.src(fontFiles)
.pipe(changed(FONTS_DIR))
.pipe(gulp.dest(FONTS_DIR));
});
gulp.task('watch-fonts', function () {
gulp.watch(fontFiles, ['build-fonts']);
});
var imageFiles = 'h/static/images/**/*';
gulp.task('build-images', function () {
gulp.src(imageFiles)
.pipe(changed(IMAGES_DIR))
.pipe(gulp.dest(IMAGES_DIR));
});
gulp.task('watch-images', function () {
gulp.watch(imageFiles, ['build-images']);
});
var MANIFEST_SOURCE_FILES = 'build/@(fonts|images|scripts|styles)/*.@(js|css|woff|jpg|png|svg)';
// Generate a JSON manifest mapping file paths to
// URLs containing cache-busting query string parameters
function generateManifest() {
var stream = gulp.src(MANIFEST_SOURCE_FILES)
.pipe(manifest({name: 'manifest.json'}))
.pipe(gulp.dest('build/'));
stream.on('end', function () {
gulpUtil.log('Updated asset manifest');
});
}
gulp.task('generate-manifest', generateManifest);
gulp.task('watch-manifest', function () {
gulp.watch(MANIFEST_SOURCE_FILES, batch(function (events, done) {
endOfStream(generateManifest(), function () {
done();
});
}));
});
gulp.task('build', function (callback) {
runSequence(['build-app-js', 'build-css',
'build-fonts', 'build-images'],
'generate-manifest',
callback);
});
gulp.task('watch', ['watch-app-js', 'watch-css',
'watch-fonts', 'watch-images',
'watch-manifest']);
window.$ = window.jQuery = require('jquery');
require('bootstrap');
Promise = require('core-js/library/es6/promise')
{
FragmentAnchor
RangeAnchor
......
Promise = require('core-js/library/es6/promise')
seek = require('dom-seek')
Annotator = require('annotator')
......
Promise = require('core-js/library/es6/promise')
baseURI = require('document-base-uri')
extend = require('extend')
raf = require('raf')
......
require('../polyfills');
var extend = require('extend');
var Annotator = require('annotator');
......
require('./polyfills')
# initialize Raven. This is required at the top of this file
# so that it happens early in the app's startup flow
settings = require('./settings')(document)
......@@ -5,9 +7,11 @@ if settings.raven
require('./raven').init(settings.raven)
require('autofill-event')
angular = require('angular')
require('angular-jwt')
# autofill-event relies on the existence of window.angular so
# it must be require'd after angular is first require'd
require('autofill-event')
streamer = require('./streamer')
......@@ -69,17 +73,24 @@ setupHttp = ['$http', ($http) ->
setupHost = ['host', (host) -> ]
module.exports = angular.module('h', [
# Angular addons which export the Angular module name
# via module.exports
require('angular-jwt')
require('angular-animate')
require('angular-resource')
require('angular-route')
require('angular-sanitize')
require('angular-toastr')
# Angular addons which do not export the Angular module
# name via module.exports
['angulartics', require('angulartics')][0]
['angulartics.google.analytics', require('angulartics/src/angulartics-ga')][0]
['ngTagsInput', require('ng-tags-input')][0]
['ui.bootstrap', require('./vendor/ui-bootstrap-custom-tpls-0.13.4')][0]
# Local addons
require('./raven').angularModule().name
'angulartics'
'angulartics.google.analytics'
'angular-jwt'
'ngAnimate'
'ngResource'
'ngRoute'
'ngSanitize'
'ngTagsInput'
'toastr'
'ui.bootstrap'
])
.controller('AppController', require('./app-controller'))
......@@ -154,3 +165,5 @@ module.exports = angular.module('h', [
.run(setupCrossFrame)
.run(setupHttp)
.run(setupHost)
require('./config/module')
Promise = require('core-js/library/es6/promise')
extend = require('extend')
RPC = require('./frame-rpc')
......
katex = require('katex')
mediaEmbedder = require('../media-embedder')
angular = require('angular')
......
/* jshint node: true */
'use strict';
var Promise = require('core-js/library/es6/promise');
var proxyquire = require('proxyquire');
var events = require('../../events');
......@@ -338,7 +337,9 @@ describe('annotation', function() {
flagEnabled: sandbox.stub().returns(true)
};
fakeFlash = sandbox.stub();
fakeFlash = {
error: sandbox.stub(),
};
fakePermissions = {
isShared: sandbox.stub().returns(true),
......@@ -1002,7 +1003,6 @@ describe('annotation', function() {
describe('deleteAnnotation() method', function() {
beforeEach(function() {
fakeAnnotationMapper.deleteAnnotation = sandbox.stub();
fakeFlash.error = sandbox.stub();
});
it(
......@@ -1079,7 +1079,6 @@ describe('annotation', function() {
var annotation;
beforeEach(function() {
fakeFlash.error = sandbox.stub();
annotation = fixtures.defaultAnnotation();
annotation.$create = sandbox.stub();
});
......@@ -1201,7 +1200,6 @@ describe('annotation', function() {
var annotation;
beforeEach(function() {
fakeFlash.error = sandbox.stub();
annotation = fixtures.defaultAnnotation();
annotation.$update = sandbox.stub();
});
......
// minimal set of polyfills for PhantomJS 1.x under Karma.
// Minimal set of polyfills for PhantomJS 1.x under Karma.
// this Polyfills:
//
// - ES5
// - ES6 Promises
// - the DOM URL API
// basic polyfills for APIs which are supported natively
// Basic polyfills for APIs which are supported natively
// by all browsers we support (IE >= 10)
require('js-polyfills/es5');
window.URL = require('js-polyfills/url').URL;
require('core-js/es5');
// additional polyfills for newer features.
// Be careful here that any added polyfills are consistent
// with what is used in builds of the app itself.
require('es6-promise');
// Additional polyfills for newer features.
// Be careful that any polyfills used here match what is used in the
// app itself.
require('./polyfills');
// disallow console output during tests
['debug', 'log', 'warn', 'error'].forEach(function (method) {
......
......@@ -21,20 +21,11 @@ module.exports = function(config) {
// Polyfills for PhantomJS
'./karma-phantomjs-polyfill.js',
// Application external deps
'../../../node_modules/jquery/dist/jquery.js',
'../../../node_modules/angular/angular.js',
'../../../node_modules/angular-animate/angular-animate.js',
'../../../node_modules/angular-resource/angular-resource.js',
'../../../node_modules/angular-route/angular-route.js',
'../../../node_modules/angular-sanitize/angular-sanitize.js',
'../../../node_modules/ng-tags-input/build/ng-tags-input.min.js',
'vendor/katex.js',
// Test deps
'../../../node_modules/angular-mocks/angular-mocks.js',
// Test setup
'./test/bootstrap.js',
// Angular directive templates
'../../templates/client/*.html',
'test/bootstrap.js',
// Tests
//
......@@ -59,6 +50,7 @@ module.exports = function(config) {
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
'./karma-phantomjs-polyfill.js': ['browserify'],
'./test/bootstrap.js': ['browserify'],
'**/*-test.js': ['browserify'],
'**/*-test.coffee': ['browserify'],
'../../templates/client/*.html': ['ng-html2js'],
......
'use strict';
// ES2015 polyfills
require('core-js/es6/promise');
require('core-js/fn/object/assign');
// URL constructor, required by IE 10/11,
// early versions of Microsoft Edge.
try {
new window.URL('https://hypothes.is');
} catch (err) {
window.URL = require('js-polyfills/url').URL;
}
// document.evaluate() implementation,
// required by IE 10, 11
//
// This sets `window.wgxpath`
if (!window.document.evaluate) {
require('./vendor/wgxpath.install');
}
'use strict';
var retry = require('retry');
var Promise = require('core-js/library/es6/promise');
/**
* Retry a Promise-returning operation until it succeeds or
......
'use strict';
var assign = require('core-js/library/fn/object/assign');
var angular = require('angular');
var Promise = require('core-js/library/es6/promise');
var events = require('./events');
var retryUtil = require('./retry-util');
......@@ -30,7 +28,7 @@ function sessionActions(options) {
};
Object.keys(actions).forEach(function (action) {
assign(actions[action], options);
Object.assign(actions[action], options);
});
return actions;
......
'use strict';
var Promise = require('core-js/library/es6/promise');
describe('annotationMapper', function() {
var sandbox = sinon.sandbox.create();
var $rootScope;
......
// Expose the sinon assertions.
sinon.assert.expose(assert, {prefix: null});
// Load Angular libraries required by tests.
//
// The tests for Annotator currently rely on having
// a full version of jQuery available and several of
// the directive tests rely on angular.element() returning
// a full version of jQuery.
//
window.jQuery = window.$ = require('jquery');
require('angular');
require('angular-resource');
require('angular-mocks');
var Promise = require('core-js/library/es6/promise');
var retryUtil = require('../retry-util');
var toResult = require('./promise-util').toResult;
......
......@@ -99,7 +99,7 @@ $base-font-size: 14px;
// frame styles
@import './bucket-bar';
@include user-select(none);
@extend .noise;
@include noise-background;
direction: ltr;
background: none;
font-size: $base-font-size;
......
......@@ -33,7 +33,7 @@ $base-line-height: 20px;
body {
$sidebar-h-padding: 9px;
@extend .noise;
@include noise-background;
font-family: $sans-font-family;
font-weight: 300;
padding: $sidebar-h-padding;
......
......@@ -26,8 +26,8 @@
//NOISE///////////
//Provides the noise background
.noise {
background: image-url("images/noise_1.png");
@mixin noise-background {
background: url("../images/noise_1.png");
}
.annotator-hide {
......
......@@ -229,7 +229,7 @@
}
.btn-danger {
@include background(linear-gradient(top, $error-color, shade($error-color, 5%)));
@include background(linear-gradient(to bottom, $error-color, shade($error-color, 5%)));
color: white;
border-color: shade($error-color, 15%);
text-shadow: 0 -1px 0 rgba(0, 0, 0, .1);
......
......@@ -26,7 +26,7 @@
// body with grey noise background and Lato font used
// in the sidebar and groups pages
.body--default {
@extend .noise;
@include noise-background;
font-family: $sans-font-family;
}
......
......@@ -34,7 +34,7 @@ $brand-color: #bd1c2b !default;
$button-text-color: $gray-dark !default;
$button-background-start: $white !default;
$button-background-end: #f0f0f0 !default;
$button-background-gradient: top, $button-background-start, $button-background-end !default;
$button-background-gradient: to bottom, $button-background-start, $button-background-end !default;
$error-color: #f0480c !default;
$success-color: #1cbd41 !default;
......
This diff is collapsed.
......@@ -17,12 +17,11 @@
"autoprefixer": "^6.0.3",
"babelify": "^6.1.3",
"bootstrap": "3.3.5",
"browserify": "^9.0.3",
"browserify": "^13.0.0",
"browserify-ngannotate": "^1.0.1",
"browserify-shim": "^3.8.3",
"clean-css": "3.3.9",
"coffee-script": "1.7.1",
"browserify-shim": "^3.8.12",
"coffeeify": "^1.0.0",
"compass-mixins": "^0.12.7",
"core-js": "^1.2.5",
"diff-match-patch": "^1.0.0",
"document-base-uri": "^1.0.0",
......@@ -30,14 +29,25 @@
"dom-anchor-text-position": "^2.0.0",
"dom-anchor-text-quote": "^2.0.0",
"dom-seek": "^1.0.1",
"es6-promise": "^3.0.2",
"end-of-stream": "^1.1.0",
"escape-html": "^1.0.3",
"exorcist": "^0.4.0",
"extend": "^2.0.0",
"gulp": "^3.9.1",
"gulp-batch": "^1.0.5",
"gulp-changed": "^1.3.0",
"gulp-cli": "^1.2.1",
"gulp-if": "^2.0.0",
"gulp-postcss": "^6.1.0",
"gulp-sass": "^2.2.0",
"gulp-sourcemaps": "^1.6.0",
"gulp-util": "^3.0.7",
"hammerjs": "^2.0.4",
"inherits": "^2.0.1",
"is-equal-shallow": "^0.1.3",
"jquery": "1.11.1",
"js-polyfills": "^0.1.11",
"mkdirp": "^0.5.1",
"ng-tags-input": "2.2.0",
"node-uuid": "^1.4.3",
"page": "^1.6.4",
......@@ -45,11 +55,16 @@
"raf": "^3.1.0",
"raven-js": "^2.0.2",
"retry": "^0.8.0",
"run-sequence": "^1.1.5",
"scroll-into-view": "^1.3.1",
"showdown": "^1.2.1",
"through2": "^2.0.1",
"tiny-emitter": "^1.0.1",
"uglify-js": "^2.4.14",
"unorm": "^1.3.3"
"uglifyify": "^3.0.1",
"unorm": "^1.3.3",
"vinyl": "^1.1.1",
"watchify": "^3.7.0",
"whatwg-fetch": "^0.10.1"
},
"devDependencies": {
"chai": "^3.2.0",
......@@ -68,14 +83,13 @@
"proxyquire": "^1.6.0",
"proxyquire-universal": "^1.0.8",
"proxyquireify": "^3.0.0",
"sinon": "1.16.1",
"whatwg-fetch": "^0.10.1"
"sinon": "1.16.1"
},
"engines": {
"node": "0.10.x"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"build-assets": "gulp build"
},
"repository": {
"type": "git",
......@@ -94,23 +108,27 @@
},
"browser": {
"annotator": "./h/static/scripts/vendor/annotator.js",
"angular": "./node_modules/angular/angular.js",
"hammerjs": "./node_modules/hammerjs/hammer.js",
"jquery": "./node_modules/jquery/dist/jquery.js"
"katex": "./h/static/scripts/vendor/katex.js",
"bootstrap-lite": "./h/static/styles/vendor/bootstrap/bootstrap.js"
},
"browserify-shim": {
"annotator": {
"exports": "Annotator",
"depends": [
"jquery"
"jquery:jQuery"
]
},
"angular": {
"exports": "global:angular",
"depends": [
"jquery"
]
},
"bootstrap-lite": {
"depends": [
"jquery:jQuery"
]
},
"hammerjs": "Hammer",
"jquery": "$"
}
......
/**
* Shared functions for creating JS code bundles using Browserify.
*/
'use strict';
var fs = require('fs');
var path = require('path');
var browserify = require('browserify');
var coffeeify = require('coffeeify');
var exorcist = require('exorcist');
var gulpUtil = require('gulp-util');
var mkdirp = require('mkdirp');
var uglifyify = require('uglifyify');
var watchify = require('watchify');
var log = gulpUtil.log;
function streamFinished(stream) {
return new Promise(function (resolve, reject) {
stream.on('finish', resolve);
stream.on('error', reject);
});
}
function waitForever() {
return new Promise(function () {});
}
/**
* type Transform = 'coffee';
*
* interface BundleOptions {
* name: string;
* path: string;
*
* entry?: string[];
* require?: string[];
* transforms: Transform[];
*
* minify?: boolean;
* }
*
* interface BuildOptions {
* watch?: boolean;
* }
*/
/**
* Generates a JavaScript application or library bundle and source maps
* for debugging.
*
* @param {BundleOptions} config - Configuration information for this bundle,
* specifying the name of the bundle, what
* modules to include and which code
* transformations to apply.
* @param {BuildOptions} buildOpts
* @return {Promise} Promise for when the bundle is fully written
* if opts.watch is false or a promise that
* waits forever otherwise.
*/
module.exports = function createBundle(config, buildOpts) {
mkdirp.sync(config.path);
buildOpts = buildOpts || {watch: false};
var bundleOpts = {
debug: true,
extensions: ['.coffee'],
// Browserify will try to detect and automatically provide
// browser implementations of Node modules.
//
// This can bloat the bundle hugely if implementations for large
// modules like 'Buffer' or 'crypto' are inadvertently pulled in.
// Here we explicitly whitelist the builtins that can be used.
//
// In particular 'Buffer' is excluded from the list of automatically
// detected variables.
//
// See node_modules/browserify/lib/builtins.js to find out which
// modules provide the implementations of these.
builtins: [
'console',
'_process',
'querystring',
],
insertGlobalVars: {
Buffer: undefined,
},
};
if (buildOpts.watch) {
bundleOpts.cache = {};
bundleOpts.packageCache = {};
}
// Specify modules that Browserify should not parse.
// The 'noParse' array must contain full file paths,
// not module names.
bundleOpts.noParse = (config.noParse || []).map(function (id) {
// If package.json specifies a custom entry point for the module for
// use in the browser, resolve that.
var packageConfig = require('../../package.json');
if (packageConfig.browser && packageConfig.browser[id]) {
return require.resolve('../../' + packageConfig.browser[id]);
} else {
return require.resolve(id);
}
});
var name = config.name;
var bundleFileName = name + '.bundle.js';
var bundlePath = config.path + '/' + bundleFileName;
var sourcemapPath = bundlePath + '.map';
var bundle = browserify([], bundleOpts);
(config.require || []).forEach(function (req) {
// When another bundle uses 'bundle.external(<module path>)',
// the module path is rewritten relative to the
// base directory and a '/' prefix is added, so
// if the other bundle contains "require('./dir/module')",
// then Browserify will generate "require('/dir/module')".
//
// In the bundle which provides './dir/module', we
// therefore need to expose the module as '/dir/module'.
if (req[0] === '.') {
bundle.require(req, {expose: req.slice(1)});
} else if (req[0] === '/') {
// If the require path is absolute, the same rules as
// above apply but the path needs to be relative to
// the root of the repository
var repoRootPath = path.join(__dirname, '../../');
var relativePath = path.relative(path.resolve(repoRootPath),
path.resolve(req));
bundle.require(req, {expose: '/' + relativePath});
} else {
// this is a package under node_modules/, no
// rewriting required.
bundle.require(req);
}
});
bundle.add(config.entry || []);
bundle.external(config.external || []);
(config.transforms || []).forEach(function (transform) {
if (transform === 'coffee') {
bundle.transform(coffeeify);
}
});
if (config.minify) {
bundle.transform({global: true}, uglifyify);
}
function build() {
var output = fs.createWriteStream(bundlePath);
var b = bundle.bundle();
b.on('error', function (err) {
log('Build error', err.toString());
});
var stream = b.pipe(exorcist(sourcemapPath))
.pipe(output);
return streamFinished(stream);
}
if (buildOpts.watch) {
bundle.plugin(watchify);
bundle.on('update', function (ids) {
var start = Date.now();
log('Source files changed', ids);
build().then(function () {
log('Updated %s (%d ms)', bundleFileName, Date.now() - start);
}).catch(function (err) {
console.error('Building updated bundle failed:', err);
});
});
build().then(function () {
log('Built ' + bundleFileName);
}).catch(function (err) {
console.error('Error building bundle:', err);
});
return waitForever();
} else {
return build();
}
};
'use strict';
var path = require('path');
var crypto = require('crypto');
var through = require('through2');
var VinylFile = require('vinyl');
/**
* Gulp plugin that generates a cache-busting manifest file.
*
* Returns a function that creates a stream which takes
* a stream of Vinyl files as inputs and outputs a JSON
* manifest mapping input paths (eg. "scripts/foo.js")
* to URLs with cache-busting query parameters (eg. "scripts/foo.js?af95bd").
*/
module.exports = function (opts) {
var manifest = {};
return through.obj(function (file, enc, callback) {
var hash = crypto.createHash('sha1');
hash.update(file.contents);
var hashSuffix = hash.digest('hex').slice(0, 6);
var relPath = path.relative('build/', file.path);
manifest[relPath] = relPath + '?' + hashSuffix;
callback();
}, function (callback) {
var manifestFile = new VinylFile({
path: opts.name,
contents: new Buffer(JSON.stringify(manifest, null, 2), 'utf-8'),
});
this.push(manifestFile);
callback();
});
};
'use strict';
/**
* Defines a set of vendor bundles which are
* libraries of 3rd-party code referenced by
* one or more bundles of the Hypothesis client/frontend.
*/
module.exports = {
bundles: {
jquery: ['jquery'],
bootstrap: ['bootstrap'],
polyfills: [require.resolve('../../h/static/scripts/polyfills')],
angular: [
'angular',
'angular-animate',
'angular-jwt',
'angular-resource',
'angular-route',
'angular-sanitize',
'ng-tags-input',
'angular-toastr',
'angulartics/src/angulartics',
'angulartics/src/angulartics-ga'
],
katex: ['katex'],
showdown: ['showdown'],
unorm: ['unorm'],
raven: ['raven-js'],
},
// List of modules to exclude from parsing for require() statements.
//
// Modules may be excluded from parsing for two reasons:
//
// 1. The module is large (eg. jQuery) and contains no require statements,
// so skipping parsing speeds up the build process.
// 2. The module is itself a compiled Browserify bundle containing
// internal require() statements, which should not be processed
// when including the bundle in another project.
noParseModules: [
'jquery',
'katex',
]
};
#!/usr/bin/env node
// post-process CSS using PostCSS
// (https://github.com/postcss/postcss)
//
// This adds vendor prefixes using autoprefixer
// https://github.com/postcss/autoprefixer
require('es6-promise').polyfill();
var autoprefixer = require('autoprefixer');
var postcss = require('postcss');
var inputCss = '';
process.stdin.on('data', function (chunk) {
inputCss += chunk;
});
process.stdin.on('end', function () {
postcss([autoprefixer])
.process(inputCss)
.then(function (result) {
console.log(result.css);
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment