diff --git a/README.md b/README.md index 2deb864fa7b2..99116ced305d 100644 --- a/README.md +++ b/README.md @@ -251,6 +251,11 @@ To use one just install for example `npm install node-sass` and rename `.css` fi The `Angular2App`'s options argument has `sassCompiler`, `lessCompiler`, `stylusCompiler` and `compassCompiler` options that are passed directly to their respective CSS preprocessors. +### Additional Preprocessor integration +- pug (pug) + +To use just run `npm install pug` and put your `.pug` files into your project. They will be compiled automatically. + ### 3rd Party Library Installation The installation of 3rd party libraries are well described at our [Wiki Page](https://github.com/angular/angular-cli/wiki/3rd-party-libs) diff --git a/lib/broccoli/angular-broccoli-pug.js b/lib/broccoli/angular-broccoli-pug.js new file mode 100644 index 000000000000..0d9cfc28e7dd --- /dev/null +++ b/lib/broccoli/angular-broccoli-pug.js @@ -0,0 +1,65 @@ +/* jshint node: true, esversion: 6 */ +'use strict'; + +const requireOrNull = require('./require-or-null'); +const Plugin = require('broccoli-caching-writer'); +const fse = require('fs-extra'); +const path = require('path'); +const Funnel = require('broccoli-funnel'); + +let pug = requireOrNull('pug'); +if (!pug) { + pug = requireOrNull(`${process.env.PROJECT_ROOT}/node_modules/pug`); +} + +class PugPlugin extends Plugin { + constructor(inputNodes, options) { + super(inputNodes, {}); + + options = options || {}; + Plugin.call(this, inputNodes, { + cacheInclude: [/\.pug$/] + }); + this.options = options; + } + + build() { + this.listFiles().forEach(fileName => { + this.compile(path.normalize(fileName), + path.normalize(this.inputPaths[0]), + path.normalize(this.outputPath)); + }); + } + + compile(fileName, inputPath, outputPath) { + const outSourceName = fileName.replace(inputPath, outputPath); + const outFileName = outSourceName.replace(/\.pug$/, '.html'); + + // We overwrite file, outFile and include the file path for the includePath. + // We also make sure the options don't include a data field. + const pugOptions = Object.assign(this.options, { + filename: fileName, + pretty: false + }); + delete pugOptions.data; + + pug.renderFile(fileName, pugOptions, (error, result) => { + if (!error) { + fse.outputFileSync(outFileName, result, 'utf-8'); + } + }) + } +} + +exports.makeBroccoliTree = (sourceDir, options) => { + options = options || {}; + + if (pug) { + let pugSrcTree = new Funnel(sourceDir, { + include: ['**/*.pug'], + allowEmpty: true + }); + + return new PugPlugin([pugSrcTree], options); + } +}; diff --git a/lib/broccoli/angular2-app.js b/lib/broccoli/angular2-app.js index 487b9bbe4771..36d3a68d936e 100644 --- a/lib/broccoli/angular2-app.js +++ b/lib/broccoli/angular2-app.js @@ -127,8 +127,8 @@ class Angular2App extends BroccoliPlugin { var buildTrees = [assetTree, tsTree, indexTree, vendorNpmTree]; - // Add available and supported CSS plugins. - for (const suffix of ['sass', 'less', 'stylus', 'compass']) { + // Add available and supported plugins. + for (const suffix of ['sass', 'less', 'stylus', 'compass', 'pug']) { const plugin = require(`./angular-broccoli-${suffix}`); const tree = plugin.makeBroccoliTree(this._inputNode, this._options[`${suffix}Compiler`]); @@ -389,7 +389,8 @@ class Angular2App extends BroccoliPlugin { '**/*.sass', '**/*.less', '**/*.styl', - '**/tsconfig.json' + '**/tsconfig.json', + '**/*.pug' ], allowEmpty: true }); diff --git a/tests/e2e/e2e_workflow.spec.js b/tests/e2e/e2e_workflow.spec.js index ea1ea1d2d920..dee36dc1be4d 100644 --- a/tests/e2e/e2e_workflow.spec.js +++ b/tests/e2e/e2e_workflow.spec.js @@ -393,6 +393,39 @@ describe('Basic end-to-end Workflow', function () { }); }); + it.skip('Installs pug support successfully', function() { + this.timeout(420000); + + sh.exec('npm install pug', { silent: true }); + return ng(['generate', 'component', 'test-component']) + .then(() => { + let componentPath = path.join(process.cwd(), 'src', 'app', 'test-component'); + let pugFile = path.join(componentPath, 'test-component-pug.component.pug'); + + expect(existsSync(componentPath)).to.be.equal(true); + sh.touch(pugFile); + expect(existsSync(pugFile)).to.be.equal(true); + let pugExample = 'h1 Test works!'; + fs.writeFileSync(pugFile, pugExample, 'utf8'); + + sh.exec(`${ngBin} build`); + let destHtml = path.join(process.cwd(), 'dist', 'app', 'test-component', 'test-component-pug.component.html'); + expect(existsSync(destHtml)).to.be.equal(true); + let contents = fs.readFileSync(destHtml, 'utf8'); + expect(contents).to.include('

Test works!

'); + + sh.rm('-f', destHtml); + process.chdir('src'); + sh.exec(`${ngBin} build`); + expect(existsSync(destHtml)).to.be.equal(true); + contents = fs.readFileSync(destHtml, 'utf8'); + expect(contents).to.include('

Test works!

'); + + process.chdir('..'); + sh.exec('npm uninstall pug', { silent: true }); + }); + }); + it('Turn on `noImplicitAny` in tsconfig.json and rebuild', function () { this.timeout(420000);