├── .gitignore ├── LICENSE ├── README.md ├── example ├── bar.js ├── es6.js ├── es6.pug ├── foo.pug ├── fooinc.pug └── params.json ├── index.js ├── package.json └── test └── bundle.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | 17 | .nyc_output 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Andrey Sidorov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pugify 2 | ====== 3 | 4 | pug transform for browserify v2. Sourcemaps generation included. 5 | 6 | ![screen shot 2013-08-28 at 5 02 16 pm](https://f.cloud.github.com/assets/173025/1040229/e0555b3e-0faf-11e3-919a-b9c0b1489077.png) 7 | 8 | 9 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/sidorares/browserify-jade/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 10 | 11 | ### Configuration 12 | 13 | #### Format 14 | ```javascript 15 | var b = browserify(); 16 | # these options are passed directly to pug compiler 17 | var pugConfig = { 18 | pretty: true, 19 | compileDebug: false 20 | }; 21 | # if babelConfig is defined, the output will be transpiled using babel, and the options are passed into babel transpiler 22 | # this is useful when you need to support old browsers, since pug compiles into ES6 format. 23 | var babelConfig = { 24 | presets: ['es2015'] 25 | }; 26 | b.transform(require('pugify').pug(pugConfig, babelConfig)); 27 | ``` 28 | 29 | If you are using pugify programatically, you can pass options to the Pug compiler by 30 | calling `pug()` on the pugify transform: 31 | 32 | ``` 33 | var b = browserify(); 34 | b.transform(require('pugify').pug({ 35 | pretty: true 36 | })); 37 | ``` 38 | 39 | If you are using pugify in a command line build, you can pass parameters by adding a 40 | "pugify" section to your package.json. You can either include parameters directly: 41 | 42 | ``` 43 | "pugify": { 44 | "pretty": true 45 | } 46 | ``` 47 | 48 | or for more complicated cases you can reference a .js file: 49 | 50 | ``` 51 | "pugify": "./assets/pugify-config.js" 52 | ``` 53 | 54 | And then in pugify-config.js: 55 | 56 | ``` 57 | module.exports = { 58 | pretty: (process.env.NODE_ENV == 'production') ? false : true 59 | }; 60 | ``` 61 | 62 | To disable sourcemap generation, which results in smaller compiled files for production builds, 63 | set pug option `compileDebug` to false in the options: 64 | 65 | ``` 66 | var b = browserify(); 67 | b.transform(require('pugify').pug({ 68 | compileDebug: false 69 | })); 70 | ``` 71 | 72 | or in package.json: 73 | 74 | ``` 75 | "pugify": { 76 | "compileDebug": false 77 | } 78 | ``` 79 | -------------------------------------------------------------------------------- /example/bar.js: -------------------------------------------------------------------------------- 1 | var tmpl = require('./foo.pug'); 2 | var data = require('./params.json'); 3 | 4 | tmpl(data); 5 | //console.log(data); 6 | -------------------------------------------------------------------------------- /example/es6.js: -------------------------------------------------------------------------------- 1 | var tmpl = require('./es6.pug'); 2 | console.log(tmpl); 3 | -------------------------------------------------------------------------------- /example/es6.pug: -------------------------------------------------------------------------------- 1 | div(class=[`class1 class2 class${id}`, `new-${id}-class`]) 2 | | something 3 | -------------------------------------------------------------------------------- /example/foo.pug: -------------------------------------------------------------------------------- 1 | include fooinc.pug 2 | 3 | // test test test 4 | //- console.log('test1') 5 | - debugger; 6 | // test test test 7 | 8 | ul 9 | each val in [1, 2, 3, 4, 5] 10 | tr 11 | //- console.log(val, val*2) 12 | - debugger 13 | td= id + val 14 | td= name 15 | 16 | 17 | p(data-bar='foo')&attributes({'data-foo':'bar'}) Paragraph with attributes 18 | 19 | 20 | - console.log(555) 21 | - var i = 1 22 | //- if (i == 1) console.log("test") 23 | - i++ 24 | - if (i == 2) debugger 25 | -------------------------------------------------------------------------------- /example/fooinc.pug: -------------------------------------------------------------------------------- 1 | h1 Header 1 2 | h2 Header 2 3 | h3 Header 3 4 | h4 Header 4 -------------------------------------------------------------------------------- /example/params.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 123, 3 | "name": "Mr Big" 4 | } 5 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | var pug = require('pug'); 4 | var through = require('through'); 5 | var transformTools = require('browserify-transform-tools'); 6 | var babel = require('@babel/core'); 7 | 8 | var SourceMapGenerator = require('source-map').SourceMapGenerator; 9 | var convert = require('convert-source-map'); 10 | 11 | 12 | var defaultPugOptions = { 13 | path: __dirname, 14 | compileDebug: true, 15 | pretty: false, 16 | }; 17 | 18 | function getTransformFn(options, babelOptions) { 19 | var key; 20 | var opts = {}; 21 | for (key in defaultPugOptions) { 22 | opts[key] = defaultPugOptions[key]; 23 | } 24 | 25 | options = options || {}; 26 | for (key in options) { 27 | opts[key] = options[key]; 28 | } 29 | 30 | return function (file) { 31 | if (!/\.(pug|jade)$/.test(file)) return through(); 32 | 33 | var data = ''; 34 | return through(write, end); 35 | 36 | function write(buf) { 37 | data += buf; 38 | } 39 | 40 | function end() { 41 | var _this = this; 42 | configData = transformTools.loadTransformConfig('pugify', file, { fromSourceFileDir: true }, function (err, configData) { 43 | if (configData) { 44 | var config = configData.config || {}; 45 | for (key in config) { 46 | opts[key] = config[key]; 47 | } 48 | } 49 | 50 | try { 51 | var result = compile(file, data, opts); 52 | if (babelOptions && Object.keys(babelOptions).length > 0 && babelOptions.constructor === Object) { 53 | result.body = babel.transform(result.body, babelOptions).code; 54 | } 55 | result.dependencies.forEach(function (dep) { 56 | _this.emit('file', dep); 57 | }); 58 | _this.queue(result.body); 59 | } catch (e) { 60 | _this.emit("error", e); 61 | } 62 | _this.queue(null); 63 | }); 64 | } 65 | }; 66 | } 67 | 68 | module.exports = getTransformFn(); 69 | module.exports.pug = getTransformFn; 70 | module.exports.jade = getTransformFn; 71 | module.exports.root = null; 72 | module.exports.register = register; 73 | 74 | function register() { 75 | require.extensions['.pug'] = require.extensions['.jade'] = function (module, filename) { 76 | var result = compile(filename, fs.readFileSync(filename, 'utf-8'), { compileDebug: true }); 77 | return module._compile(result.body, filename); 78 | }; 79 | } 80 | 81 | function replaceMatchWith(match, newContent) { 82 | var src = match.input; 83 | return src.slice(0, match.index) + newContent + src.slice(match.index + match[0].length); 84 | } 85 | 86 | function withSourceMap(src, compiled, name) { 87 | 88 | var compiledLines = compiled.split('\n'); 89 | var generator = new SourceMapGenerator({ file: name + '.js' }); 90 | 91 | compiledLines.forEach(function (l, lineno) { 92 | var oldFormat = false; 93 | var generatedLine; 94 | var linesMatched = {}; 95 | linesMatched[name] = {}; 96 | 97 | var m = l.match(/;pug_debug_line = ([0-9]+);/); 98 | if (m) { 99 | var originalLine = Number(m[1]); 100 | 101 | if (originalLine > 0) { 102 | var fname = ""; 103 | m = l.match(/;pug_debug_filename = \"(.*)\";/); 104 | if (m) { 105 | fname = m[1].replace(/\\u002F/g, "/"); 106 | } else { 107 | fname = name; 108 | } 109 | if (!linesMatched[fname]) { 110 | // new include file - add to sourcemap 111 | linesMatched[fname] = {}; 112 | try { 113 | var srcContent = fs.readFileSync(fname, 'utf-8'); 114 | generator.setSourceContent(fname, srcContent); 115 | } catch (e) {} 116 | } 117 | var alreadyMatched = linesMatched[fname][originalLine]; 118 | 119 | if (!alreadyMatched && 120 | (!/^;pug_debug/.test(compiledLines[lineno + 1]))) 121 | generatedLine = lineno + 3; // 1-based and allow for PREFIX extra line 122 | 123 | if (generatedLine) { 124 | linesMatched[fname][originalLine] = true; 125 | 126 | generator.addMapping({ 127 | generated: { 128 | line: generatedLine, 129 | column: 0 130 | }, 131 | source: fname, 132 | original: { 133 | line: originalLine, 134 | column: 0 135 | } 136 | }); 137 | } 138 | } 139 | } 140 | 141 | //remove pug debug lines from within generated code 142 | var debugRe = /;pug_debug_line = [0-9]+;(pug_debug_filename = ".*";)?/; 143 | var match; 144 | while (match = l.match(debugRe)) { 145 | l = replaceMatchWith(match, ''); 146 | } 147 | compiledLines[lineno] = l; 148 | }); 149 | 150 | // Remove pug debug lines at beginning and end of compiled version 151 | // could be in a number of first few lines depending on source content 152 | var found = false; 153 | var line = 0; 154 | var re = /var\spug_debug_filename.*/; 155 | while (!found && line < compiledLines.length) { 156 | var lnDebug = compiledLines[line]; 157 | if (re.test(lnDebug)) { 158 | found = true; 159 | compiledLines[line] = lnDebug.replace(re, ''); 160 | } 161 | line++; 162 | } 163 | if (found) { 164 | var ln = compiledLines.length; 165 | compiledLines[ln - 1] = compiledLines[ln - 1].replace(/\} catch \(err\)[^}]*};/, ''); 166 | } 167 | 168 | generator.setSourceContent(name, src); 169 | 170 | var map = convert.fromJSON(generator.toString()); 171 | compiledLines.push(map.toComment()); 172 | return compiledLines.join('\n'); 173 | } 174 | 175 | function compile(file, template, options) { 176 | options.filename = file; 177 | options.name = "template"; 178 | var result; 179 | result = pug.compileClientWithDependenciesTracked(template, options); 180 | if (options.compileDebug) 181 | result.body = withSourceMap(template, result.body, file); 182 | 183 | var PREFIX = "var pug = require('"+(options.runtimePath === undefined ? "pug-runtime" : options.runtimePath)+"');\nmodule.exports=template;"; 184 | 185 | result.body = PREFIX + result.body; 186 | return result; 187 | } 188 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pugify", 3 | "version": "2.2.0", 4 | "description": "browserify v2 plugin for pug with sourcemaps support", 5 | "main": "index.js", 6 | "dependencies": { 7 | "@babel/core": "^7.13.10", 8 | "browserify-transform-tools": "^1.7.0", 9 | "convert-source-map": "^1.7.0", 10 | "pug": "^3.0.2", 11 | "source-map": "^0.7.3", 12 | "through": "^2.3.8" 13 | }, 14 | "devDependencies": { 15 | "@babel/preset-env": "^7.13.10", 16 | "browserify": "^17.0.0", 17 | "tap": "14.11.0" 18 | }, 19 | "scripts": { 20 | "test": "tap test/*.js" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git://github.com/sidorares/pugify.git" 25 | }, 26 | "homepage": "https://github.com/sidorares/pugify", 27 | "keywords": [ 28 | "browserify", 29 | "v2", 30 | "jade", 31 | "pug", 32 | "plugin", 33 | "transform", 34 | "source maps", 35 | "sourcemaps" 36 | ], 37 | "contributors": [ 38 | { 39 | "name": "Andrey Sidorov", 40 | "email": "sidorares@yandex.ru" 41 | }, 42 | { 43 | "name": "David Willis" 44 | } 45 | ], 46 | "license": "MIT" 47 | } 48 | -------------------------------------------------------------------------------- /test/bundle.js: -------------------------------------------------------------------------------- 1 | var test = require('tap').test; 2 | var browserify = require('browserify'); 3 | var fs = require('fs'); 4 | var vm = require('vm'); 5 | var path = require('path'); 6 | var SourceMapConsumer = require('source-map').SourceMapConsumer; 7 | var convert = require('convert-source-map'); 8 | var pugify = require('../index.js'); 9 | 10 | test('no options bundle', function(t) { 11 | t.plan(1); 12 | var b = browserify(); 13 | b.add(__dirname + '/../example/bar.js'); 14 | b.transform(__dirname + '/..'); 15 | b.bundle(function (err, src) { 16 | if (err) t.fail(err); 17 | testBundle(src, t); 18 | }); 19 | }); 20 | 21 | test('options bundle', function(t) { 22 | t.plan(1); 23 | var b = browserify(); 24 | b.add(__dirname + '/../example/bar.js'); 25 | b.transform(pugify.pug({ 26 | pretty: false 27 | })); 28 | b.bundle(function (err, src) { 29 | if (err) t.fail(err); 30 | testBundle(src, t); 31 | }); 32 | }); 33 | 34 | test('with sourcemap', function (t) { 35 | 36 | async function checkSrcContainsSourceMapFor (src, filename) { 37 | var json = convert 38 | .fromSource(src.toString('utf8')) 39 | .toJSON(); 40 | var consumer = await new SourceMapConsumer(json); 41 | try { 42 | var srcFile = path.join(__dirname, '../example', filename); 43 | var smSrc = consumer.sourceContentFor(srcFile); 44 | var fileSource = await fs.promises.readFile(srcFile, 'utf8'); 45 | if (smSrc !== fileSource) throw new Error('Sourcemap does not match file content'); 46 | } finally { 47 | consumer.destroy(); 48 | } 49 | } 50 | 51 | t.plan(1); 52 | var b = browserify({debug:true}); 53 | b.add(__dirname + '/../example/bar.js'); 54 | b.transform(pugify.pug({ 55 | pretty: true, 56 | compileDebug: true 57 | })); 58 | b.bundle(async function (err, src) { 59 | if (err) t.fail(err); 60 | try { 61 | await Promise.all([ 62 | checkSrcContainsSourceMapFor(src, 'foo.pug'), 63 | checkSrcContainsSourceMapFor(src, 'fooinc.pug'), 64 | ]); 65 | testBundle(src, t); 66 | } catch (err) { 67 | t.fail(err); 68 | } 69 | }); 70 | }); 71 | 72 | test('bundle with babel transpiling options', function(t) { 73 | var b = browserify(); 74 | b.add(__dirname + '/../example/es6.js'); 75 | b.transform(pugify.pug({ pretty: false }, { presets: ['@babel/preset-env'] })); 76 | 77 | b.bundle(function (err, src) { 78 | if (err) t.fail(err); 79 | function log(msg) { 80 | // verify that there is no string template in the compiled javascript object, which is a feature of ES6 81 | t.notOk(/`/.test(msg)); 82 | } 83 | vm.runInNewContext(src, { 84 | console: { log: log } 85 | }); 86 | t.end(); 87 | }); 88 | }); 89 | 90 | function testBundle(src, t) { 91 | function log (msg) { 92 | t.equal(msg, 555); 93 | } 94 | vm.runInNewContext(src, { 95 | console: { log: log } 96 | }); 97 | } 98 | --------------------------------------------------------------------------------