From 718665317d5fa19b495aad364f6886395b5f93c5 Mon Sep 17 00:00:00 2001 From: Marius Petcu Date: Wed, 24 Jun 2015 14:49:27 +0300 Subject: [PATCH] Mixin loader and client-side mixin running --- .../lib}/jade-fast-compiler.js | 11 ++-- plugins/lfa-compilation/tasks/gulp-stylus2.js | 62 ------------------- .../web_modules/mixin-loader.js | 23 +++++++ .../lfa-core/frontend/js/mixin-compiler.js | 36 +++++++++++ plugins/lfa-core/frontend/js/views/chapter.js | 5 +- plugins/lfa-userspace/frontend/js/index.js | 7 ++- plugins/lfa-userspace/frontend/js/mixins.js | 8 +++ plugins/lfa-userspace/frontend/js/userjs.js | 4 +- .../lfa-userspace/frontend/mixins/index.jade | 15 +++-- ...lace-loader.js => replace-project-path.js} | 3 +- plugins/lfa-userspace/tasks/safe-eval.js | 5 ++ plugins/lfa-userspace/tasks/text-jade.js | 50 ++++++++------- 12 files changed, 124 insertions(+), 105 deletions(-) rename plugins/{lfa-userspace/tasks => lfa-compilation/lib}/jade-fast-compiler.js (84%) delete mode 100644 plugins/lfa-compilation/tasks/gulp-stylus2.js create mode 100644 plugins/lfa-compilation/web_modules/mixin-loader.js create mode 100644 plugins/lfa-core/frontend/js/mixin-compiler.js create mode 100644 plugins/lfa-userspace/frontend/js/mixins.js rename plugins/lfa-userspace/loaders/{replace-loader.js => replace-project-path.js} (52%) create mode 100644 plugins/lfa-userspace/tasks/safe-eval.js diff --git a/plugins/lfa-userspace/tasks/jade-fast-compiler.js b/plugins/lfa-compilation/lib/jade-fast-compiler.js similarity index 84% rename from plugins/lfa-userspace/tasks/jade-fast-compiler.js rename to plugins/lfa-compilation/lib/jade-fast-compiler.js index d330e72..a7e06fb 100644 --- a/plugins/lfa-userspace/tasks/jade-fast-compiler.js +++ b/plugins/lfa-compilation/lib/jade-fast-compiler.js @@ -14,8 +14,6 @@ function extractMixins(r) { JadeFastCompiler.newContext = function newContext() { return { jade: jade, - jade_mixins: {}, - self: {}, }; }; @@ -28,13 +26,14 @@ JadeFastCompiler.shimmedContext = function shimmedContext() { JadeFastCompiler.extensibleBundle = function extensibleBundle(code) { var template = [ '(function template(context, locals, use_buf) {', - 'var buf = [];', + 'var buf = context.buf = context.buf || [];', + 'buf.length = 0;', 'var jade = context.jade;', 'var jade_mixins = context.jade_mixins = context.jade_mixins || {};', 'var jade_interp;', - 'var self = context.self = ((locals === false) ? context.self : (locals || {}));', - code, - 'if (use_buf === false) { return buf.join(""); }', + 'context.self = ((locals === false) ? context.self : locals) || {};', + code.replace(/self/g, 'context.self'), + 'if (use_buf !== false) { return buf.join(""); }', '})' ].join('\n'); return template; diff --git a/plugins/lfa-compilation/tasks/gulp-stylus2.js b/plugins/lfa-compilation/tasks/gulp-stylus2.js deleted file mode 100644 index 3a9b009..0000000 --- a/plugins/lfa-compilation/tasks/gulp-stylus2.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -var through = require('through2'); -var stylus = require('accord').load('stylus'); -var gutil = require('gulp-util'); -var rext = require('replace-ext'); -var path = require('path'); -var _ = require('lodash'); -var applySourceMap = require('vinyl-sourcemaps-apply'); - -var PLUGIN_NAME = 'gulp-stylus'; - -module.exports = function (options) { - var opts = _.assign({}, options); - - return through.obj(function (file, enc, cb) { - - if (file.isStream()) { - return cb(new gutil.PluginError(PLUGIN_NAME, 'Streaming not supported')); - } - if (file.isNull()) { - return cb(null, file); - } - if (path.extname(file.path) === '.css') { - return cb(null, file); - } - if (file.sourceMap) { - opts.sourcemap = true; - } - opts.filename = file.path; - - var fileOpts = opts; - if (file.stylusOpts) { - fileOpts = _.extend({}, opts, file.stylusOpts); - } - - stylus.render(file.contents.toString('utf8'), fileOpts) - .then(function(res) { - if (res.result !== undefined) { - file.path = rext(file.path, '.css'); - file.contents = new Buffer(res.result); - if (res.sourcemap) { - res.sourcemap.file = res.sourcemap.file || path.basename(file.path); - makePathsRelative(file, res.sourcemap); - applySourceMap(file, res.sourcemap); - file.sourceMap.file = file.relative; - } - return cb(null, file); - } - }) - .catch(function(err) { - return cb(new gutil.PluginError(PLUGIN_NAME, err)); - }); - }); - -}; - -function makePathsRelative(file, sourcemap) { - for (var i = 0; i < sourcemap.sources.length; i++) { - sourcemap.sources[i] = path.relative(file.base, sourcemap.sources[i]); - } -} diff --git a/plugins/lfa-compilation/web_modules/mixin-loader.js b/plugins/lfa-compilation/web_modules/mixin-loader.js new file mode 100644 index 0000000..f815b97 --- /dev/null +++ b/plugins/lfa-compilation/web_modules/mixin-loader.js @@ -0,0 +1,23 @@ +var JadeFastCompiler = require('../lib/jade-fast-compiler'); + +module.exports = function (content) { + this.cacheable(true); + var next = this.async(); + + var opts = { filename: this.resourcePath }; + + JadeFastCompiler.compileBundle(content, opts) + .then(function (bundle) { + var newContent = [ + 'var lfaMixins = window.lfaMixins = window.lfaMixins || [];\n', + 'var mixins = ', bundle, ';\n', + 'lfaMixins.push(mixins);\n', + 'module.exports = mixins;\n', + ].join(''); + + next(null, newContent); + }) + .catch(function (err) { + next(err); + }); +}; diff --git a/plugins/lfa-core/frontend/js/mixin-compiler.js b/plugins/lfa-core/frontend/js/mixin-compiler.js new file mode 100644 index 0000000..ef9ab0c --- /dev/null +++ b/plugins/lfa-core/frontend/js/mixin-compiler.js @@ -0,0 +1,36 @@ +var jadeRuntime = require('jade/lib/runtime'); +var _ = require('lodash'); + +var jadeContext = null; + +function buildContext() { + jadeContext = { + jade: jadeRuntime, + }; + + _.each(window.lfaMixins || [], function (mixins) { + mixins(jadeContext, false, false); + }); +} + +module.exports = function (template) { + try { + if (!jadeContext) { + buildContext(); + } + + return template(jadeContext); + } catch (ex) { + var trace = ex.stack || ex.toString(); + return [ + '
', + '

', + 'Chapter loading error', + '

', + '
',
+          trace,
+        '
', + '
', + ].join(''); + } +}; diff --git a/plugins/lfa-core/frontend/js/views/chapter.js b/plugins/lfa-core/frontend/js/views/chapter.js index 03c8f3f..e2bbfe6 100644 --- a/plugins/lfa-core/frontend/js/views/chapter.js +++ b/plugins/lfa-core/frontend/js/views/chapter.js @@ -5,6 +5,7 @@ require('stacktable'); require('fluidbox'); var templates = require('templates'); +var mixinCompiler = require('../mixin-compiler'); var Chapters = require('../chapters'); var Prefetcher = require('../prefetcher'); var App = require('../app'); @@ -119,7 +120,7 @@ var ChapterView = Backbone.View.extend({ Chapters.asyncLoad(chapter, this.chapterLoaded.bind(this, chapter)); }, - chapterLoaded: function(chapter, error, htmlData) { + chapterLoaded: function(chapter, error, template) { // For when the chapters come in the wrong order if (App.book.currentChapter !== chapter) { return; } @@ -128,7 +129,7 @@ var ChapterView = Backbone.View.extend({ }); var front, back; - var data = htmlData(); + var data = mixinCompiler(template); if (data && data.indexOf("
") !== -1) { front = "
"; back = "
"; diff --git a/plugins/lfa-userspace/frontend/js/index.js b/plugins/lfa-userspace/frontend/js/index.js index 035a9bb..43325e0 100644 --- a/plugins/lfa-userspace/frontend/js/index.js +++ b/plugins/lfa-userspace/frontend/js/index.js @@ -1,5 +1,10 @@ +require('!!../../loaders/replace-project-path.js!./mixins.js'); +require('!!mixin-loader!../mixins/index.jade'); + module.exports = { BuildInfo: require('build-info'), HotChapterReload: require('./hot-chapter-reload'), - UserJS: require('!!../../loaders/replace-loader.js!./userjs.js'), + UserJS: require('!!../../loaders/replace-project-path.js!./userjs.js'), }; + +console.log(module.exports); diff --git a/plugins/lfa-userspace/frontend/js/mixins.js b/plugins/lfa-userspace/frontend/js/mixins.js new file mode 100644 index 0000000..ae2536b --- /dev/null +++ b/plugins/lfa-userspace/frontend/js/mixins.js @@ -0,0 +1,8 @@ +var projectMixinsModuleId = null; +try { + projectMixinsModuleId = require.resolve('!!mixin-loader!__PROJ_PATH__/mixins/index.jade'); +} catch (ex) {} + +if (projectMixinsModuleId !== null) { + __webpack_require__(projectMixinsModuleId); +} diff --git a/plugins/lfa-userspace/frontend/js/userjs.js b/plugins/lfa-userspace/frontend/js/userjs.js index d97b713..9e36b77 100644 --- a/plugins/lfa-userspace/frontend/js/userjs.js +++ b/plugins/lfa-userspace/frontend/js/userjs.js @@ -1,9 +1,9 @@ var userModuleId = null; try { - var userModuleId = resolve('__REPLACE__'); + var userModuleId = require.resolve('__PROJ_PATH__/js'); } catch (ex) {} if (userModuleId !== null) { - __webpack_require__(userModuleId); + module.exports = __webpack_require__(userModuleId); } diff --git a/plugins/lfa-userspace/frontend/mixins/index.jade b/plugins/lfa-userspace/frontend/mixins/index.jade index a1f73e8..eb9a0ff 100644 --- a/plugins/lfa-userspace/frontend/mixins/index.jade +++ b/plugins/lfa-userspace/frontend/mixins/index.jade @@ -1,12 +1,15 @@ +mixin meta(key, value) + - if (self.meta) { + - self.meta[key] = value; + - } + mixin title(title, subtitle) - - self.meta.title = title; - - if (subtitle) { self.meta.subtitle = subtitle; } + +meta('title', title) + if (subtitle) + +meta('subtitle', subtitle) mixin subtitle(subtitle) - - self.meta.subtitle = subtitle; - -mixin meta(key, value) - - self.meta[key] = value; + +meta('subtitle', subtitle) mixin hidden_from_toc() +meta('noToC', true) diff --git a/plugins/lfa-userspace/loaders/replace-loader.js b/plugins/lfa-userspace/loaders/replace-project-path.js similarity index 52% rename from plugins/lfa-userspace/loaders/replace-loader.js rename to plugins/lfa-userspace/loaders/replace-project-path.js index c979991..28c6f5d 100644 --- a/plugins/lfa-userspace/loaders/replace-loader.js +++ b/plugins/lfa-userspace/loaders/replace-project-path.js @@ -3,6 +3,5 @@ var path = require('path'); module.exports = function (content) { this.cacheable(true); var lfa = this.options.lfa; - var userJsPath = path.join(lfa.config.projectPath, 'js'); - return content.replace(/__REPLACE__/, userJsPath); + return content.replace(/__PROJ_PATH__/, lfa.config.projectPath); }; diff --git a/plugins/lfa-userspace/tasks/safe-eval.js b/plugins/lfa-userspace/tasks/safe-eval.js new file mode 100644 index 0000000..51e8dfb --- /dev/null +++ b/plugins/lfa-userspace/tasks/safe-eval.js @@ -0,0 +1,5 @@ +module.exports = function (f) { + var x; + eval('x = ' + f); + return x; +}; diff --git a/plugins/lfa-userspace/tasks/text-jade.js b/plugins/lfa-userspace/tasks/text-jade.js index 2f5169a..54f0300 100644 --- a/plugins/lfa-userspace/tasks/text-jade.js +++ b/plugins/lfa-userspace/tasks/text-jade.js @@ -1,4 +1,4 @@ -var JadeFastCompiler = require('./jade-fast-compiler'); +var JadeFastCompiler = require('../../lfa-compilation/lib/jade-fast-compiler'); var through = require('through2'); var gutil = require('gulp-util'); var path = require('path'); @@ -7,6 +7,7 @@ var _ = require('lodash'); var when = require('when'); var nodefn = require('when/node'); var fs = require('fs'); +var safeEval = require('./safe-eval'); var PLUGIN_NAME = 'text-jade'; var boilerplate = [ @@ -24,7 +25,7 @@ function escapeRegExp(string) { // minimal jade context, loaded only with the +meta mixins var jadeContext = null; function getJadeContext() { - if (jadeContext) { return jadeContext; } + if (jadeContext) { return when(jadeContext); } jadeContext = JadeFastCompiler.shimmedContext(); @@ -34,8 +35,7 @@ function getJadeContext() { return JadeFastCompiler.compileBundle(contents.toString('utf8'), { filename: fpath }); }) .then(function (template) { - var mixins; - eval('mixins = ' + template); + var mixins = safeEval(template); mixins(jadeContext, false, false); return jadeContext; }); @@ -61,9 +61,7 @@ module.exports = function textJadeTasks(lfa) { when .try(function () { - return JadeFastCompiler.compileBundle( - file.contents.toString('utf8'), - { filename: file.path }); + return JadeFastCompiler.compileBundle(file.contents.toString('utf8'), { filename: file.path }); }) .then(function (template) { // We're now trying to extract the +meta calls from the template @@ -73,28 +71,32 @@ module.exports = function textJadeTasks(lfa) { var shimmedFunction = function () {}; try { - eval('shimmedFunction = ' + template); + shimmedFunction = safeEval(template); } catch (err) { err.fileName = file.path; throw err; } - // Running the jade file now will probably fail, especially since - // there are no mixins loaded, but +meta calls are usually at the start - // of the file and we will reach them without failing - try { - shimmedFunction(getJadeContext(), locals, false); - } catch (ex) {} - - boilerplate[1] = url; - boilerplate[3] = template; - var newFile = new File({ - base: '', - history: file.history.concat([path.join('chapters', url + '.js')]), - contents: new Buffer(boilerplate.join(''), 'utf8'), - }); - newFile.textMeta = locals.meta; - cb(null, newFile); + return getJadeContext() + .then(function (context) { + // Running the jade file now will probably fail, especially since + // there are no mixins loaded, but +meta calls are usually at the start + // of the file and we will reach them without failing + try { shimmedFunction(context, locals, false); } catch (ex) {} + }) + .then(function () { + boilerplate[1] = url; + boilerplate[3] = template; + + var newFile = new File({ + base: '', + history: file.history.concat([path.join('chapters', url + '.js')]), + contents: new Buffer(boilerplate.join(''), 'utf8'), + }); + + newFile.textMeta = locals.meta; + cb(null, newFile); + }); }) .catch(function(err) { return cb(new gutil.PluginError(PLUGIN_NAME, err));