diff --git a/.jshintignore b/.jshintignore new file mode 100644 index 0000000..dbf0821 --- /dev/null +++ b/.jshintignore @@ -0,0 +1 @@ +node_modules/* \ No newline at end of file diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 30d74d2..0000000 --- a/.npmignore +++ /dev/null @@ -1 +0,0 @@ -test \ No newline at end of file diff --git a/README.md b/README.md index 006da1c..5355909 100644 --- a/README.md +++ b/README.md @@ -10,69 +10,78 @@ Webpack plugin to generate a sitemap. Designed to work with [static-site-generat ## Usage -Add to your webpack config -- see below for an example. The plugin signature is: +Add to your webpack config -- see below for examples. The plugin signature is: - new SitemapPlugin(base, paths, filename) + new SitemapPlugin(base, paths, options) * `base` is the root URL of your site (e.g. 'https://mysite.com') -* `paths` is the array of locations on your site -- this can be the same one you pass to `static-site-generator-webpack-plugin` -* `filename` is the name of the output file -- the default is `sitemap.xml` +* `paths` is the array of locations on your site -- this can be the same one you pass to `static-site-generator-webpack-plugin`. If you want to customize each path in the sitemap, you can also pass an array of objects; objects must have a `path` attribute and may have a `lastMod`, `priority`, and/or `changeFreq` attribute. (However, an array of objects will not be directly compatible with `static-site-generator-webpack-plugin`.) +* `options` is an optional object of configurable options -- see below ### Options -* `lastMod` [boolean] default `false` -* `priority` [number] default `null` -* `changeFreq` [string] default `null`, list of applicable values based on [sitemaps.org](http://www.sitemaps.org/protocol.html) - -``` -always -hourly -daily -weekly -monthly -yearly -never -``` +* `fileName` (string) -- default `sitemap.xml` -- name of the output file +* `lastMod` (boolean) -- default `false` -- whether to include the current date as the ``. Can be overridden by path-specific `lastMod`. +* `priority` (number) -- default `null` -- a `` to be set globally on all locations. Can be overridden by path-specific `priority`. +* `changeFreq` (string) -- default `null` -- a `` to be set globally on all locations; list of applicable values based on [sitemaps.org](http://www.sitemaps.org/protocol.html): `always`, `hourly`, `daily`, `weekly`, `monthly`, `yearly`, `never`. Can be overridden by path-specific `changeFreq`. ### webpack.config.js ```js var SitemapPlugin = require('sitemap-webpack-plugin'); +/* basic paths -- directly compatible with static-site-generator-webpack-plugin */ var paths = [ '/foo/', '/bar/' ]; +/* object paths -- more fine-grained but not directly compatible with static-site-generator-webpack-plugin */ +/* object paths must have a `path` attribute -- others are optional, and fall back to global config (if any) */ +var paths = [ + { + path: '/foo/', + lastMod: '2015-01-04', + priority: '0.8', + changeFreq: 'monthly' + } +]; + +/* you can also convert object paths back into static-site-generator-webpack-plugin compatible paths */ +var staticSiteGeneratorCompatiblePaths = paths.map(function(path) { return path.path }); + module.exports = { /* snip */ + /* basic (output defaults to sitemap.xml) */ plugins: [ - new SitemapPlugin('https://mysite.com', paths, 'map.xml') + new SitemapPlugin('https://mysite.com', paths) ] - /* with options */ + /* with custom output filename */ + plugins: [ + new SitemapPlugin('https://mysite.com', paths, { + fileName: 'map.xml' + }) + ] + /* with global options */ plugins: [ - new SitemapPlugin( - 'https://mysite.com',paths, 'map.xml', - { - lastMod: true, - changeFreq: 'monthly', - priority: 0.4, - } - ) + new SitemapPlugin('https://mysite.com', paths, { + fileName: 'map.xml', + lastMod: true, + changeFreq: 'monthly', + priority: '0.4' + }) ] }; ``` - - ## Contributing -1. Fork it ( https://github.com/schneidmaster/sitemap-webpack-plugin/fork ) +1. Fork it (https://github.com/schneidmaster/sitemap-webpack-plugin/fork) 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) diff --git a/circle.yml b/circle.yml index 6b6d5d1..e9f5b5a 100644 --- a/circle.yml +++ b/circle.yml @@ -1,3 +1,6 @@ test: + override: + - npm test + - npm run lint post: - npm run coveralls diff --git a/date.js b/date.js index 52eac43..093805d 100644 --- a/date.js +++ b/date.js @@ -5,4 +5,4 @@ module.exports = function() { date.splice(0, 0, year); var formattedDate = date.join("-"); return formattedDate; -} +}; diff --git a/index.js b/index.js index 096f265..48c1d7a 100644 --- a/index.js +++ b/index.js @@ -1,46 +1,103 @@ var GenerateDate = require('./date'); -function SitemapWebpackPlugin(base, paths, fileName) { - var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; +var GenerateSitemap = function(self) { + // Validate configuration + if(typeof(self.base) !== 'string') { + throw new Error('Provided base URL is not a string'); + } else if(self.base.substr(-1) === '/') { + self.base = self.base.replace(/\/$/, ''); + } + if(!Array.isArray(self.paths)) { + throw new Error('Provided paths are not an array'); + } + + // Create sitemap from paths + var out = ''; + out += ''; + + var locs = self.paths.map(function(path) { + if(typeof(path) === 'object') { + if(typeof(path.path) !== 'string') { + throw new Error('Path is not a string: ' + path); + } + } else if(typeof(path) === 'string') { + path = { path: path }; + } else { + throw new Error('Path is not a string: ' + path); + } + + var loc = ''; + + var stringPath = path.path; + if(stringPath.substr(0, 1) !== '/') { + stringPath = '/' + path.path; + } + loc += '' + self.base + stringPath + ''; + + // Add loc lastMod or default if set. + if(path.lastMod) { + loc += '' + path.lastMod + ''; + } else if(self.lastMod) { + loc += '' + GenerateDate() + ''; + } + + // Add loc changeFreq or default if set. + if(path.changeFreq) { + loc += '' + path.changeFreq + ''; + } else if(self.changeFreq) { + loc += '' + self.changeFreq + ''; + } + + // Add loc priority or default if set. + if(path.priority) { + loc += '' + path.priority + ''; + } else if(self.priority) { + loc += '' + self.priority + ''; + } + + loc += ''; + return loc; + }); + + out += locs.join(''); + out += ''; + return out; +}; + +function SitemapWebpackPlugin(base, paths, options) { + // Set mandatory values + this.base = base; + this.paths = paths; // Set options + if(typeof(options) === 'undefined') { + options = {}; + } + this.fileName = options.fileName || 'sitemap.xml'; this.lastMod = options.lastMod || false; this.changeFreq = options.changeFreq || null; this.priority = options.priority || null; - - this.base = base; - this.paths = paths; - this.fileName = fileName || 'sitemap.xml'; } SitemapWebpackPlugin.prototype.apply = function(compiler) { var self = this; - // Create sitemap from paths - var out = ''; - out += ''; - for(var i = 0; i < self.paths.length; i++) { - var path = self.paths[i]; - - out += ''; - out += '' + self.base + path + ''; - self.lastMod ? out += '' + GenerateDate() + '' : null; - self.changeFreq ? out += '' + self.changeFreq + '' : null; - self.priority ? out += '' + self.priority + '' : null; - out += ''; - } - out += ''; - compiler.plugin('emit', function(compilation, callback) { - compilation.fileDependencies.push(self.fileName); - compilation.assets[self.fileName] = { - source: function () { - return out; - }, - size: function () { - return Buffer.byteLength(out, 'utf8'); - } - }; + try { + var sitemap = GenerateSitemap(self); + + compilation.fileDependencies.push(self.fileName); + compilation.assets[self.fileName] = { + source: function () { + return sitemap; + }, + size: /* istanbul ignore next */ function () { + return Buffer.byteLength(out, 'utf8'); + } + }; + } catch (err) { + compilation.errors.push(err.stack); + } callback(); }); }; diff --git a/package.json b/package.json index eff233b..368a2c3 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,15 @@ { "name": "sitemap-webpack-plugin", - "version": "0.1.2", + "version": "0.2.0", "description": "Webpack plugin to generate a sitemap.", "main": "index.js", "scripts": { "test": "istanbul cover _mocha test -- --timeout 20000", - "coveralls": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage" + "coveralls": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage", + "lint": "jshint ./**/*.js" + }, + "jshintConfig": { + "expr": true }, "repository": { "type": "git", @@ -29,6 +33,7 @@ "coveralls": "^2.11.15", "glob": "^7.1.1", "istanbul": "^0.4.5", + "jshint": "^2.9.4", "mocha": "^3.2.0", "rimraf": "^2.5.4", "timecop": "git+https://github.com/jamesarosen/Timecop.js.git", diff --git a/test/error-cases/invalid-path/desc.js b/test/error-cases/invalid-path/desc.js new file mode 100644 index 0000000..328f35c --- /dev/null +++ b/test/error-cases/invalid-path/desc.js @@ -0,0 +1 @@ +module.exports = 'Invalid path in paths array'; diff --git a/test/error-cases/invalid-path/expected-error.js b/test/error-cases/invalid-path/expected-error.js new file mode 100644 index 0000000..7bc6983 --- /dev/null +++ b/test/error-cases/invalid-path/expected-error.js @@ -0,0 +1 @@ +module.exports = 'Error: Path is not a string: 42'; \ No newline at end of file diff --git a/test/error-cases/invalid-path/webpack.config.js b/test/error-cases/invalid-path/webpack.config.js new file mode 100644 index 0000000..bb3dd1c --- /dev/null +++ b/test/error-cases/invalid-path/webpack.config.js @@ -0,0 +1,13 @@ +var SitemapPlugin = require('../../../'); + +module.exports = { + output: { + filename: 'index.js', + path: __dirname + '/actual-output', + libraryTarget: 'umd' + }, + + plugins: [ + new SitemapPlugin('https://mysite.com', [42]) + ] +}; diff --git a/test/error-cases/invalid-paths/desc.js b/test/error-cases/invalid-paths/desc.js new file mode 100644 index 0000000..5fb9aec --- /dev/null +++ b/test/error-cases/invalid-paths/desc.js @@ -0,0 +1 @@ +module.exports = 'Invalid object passed for paths array'; diff --git a/test/error-cases/invalid-paths/expected-error.js b/test/error-cases/invalid-paths/expected-error.js new file mode 100644 index 0000000..81abd48 --- /dev/null +++ b/test/error-cases/invalid-paths/expected-error.js @@ -0,0 +1 @@ +module.exports = 'Error: Provided paths are not an array'; \ No newline at end of file diff --git a/test/error-cases/invalid-paths/webpack.config.js b/test/error-cases/invalid-paths/webpack.config.js new file mode 100644 index 0000000..5d7017c --- /dev/null +++ b/test/error-cases/invalid-paths/webpack.config.js @@ -0,0 +1,13 @@ +var SitemapPlugin = require('../../../'); + +module.exports = { + output: { + filename: 'index.js', + path: __dirname + '/actual-output', + libraryTarget: 'umd' + }, + + plugins: [ + new SitemapPlugin('https://mysite.com', 'fail') + ] +}; diff --git a/test/error-cases/missing-base/desc.js b/test/error-cases/missing-base/desc.js new file mode 100644 index 0000000..159f460 --- /dev/null +++ b/test/error-cases/missing-base/desc.js @@ -0,0 +1 @@ +module.exports = 'Base URL is not a string'; diff --git a/test/error-cases/missing-base/expected-error.js b/test/error-cases/missing-base/expected-error.js new file mode 100644 index 0000000..af5e5eb --- /dev/null +++ b/test/error-cases/missing-base/expected-error.js @@ -0,0 +1 @@ +module.exports = 'Error: Provided base URL is not a string'; \ No newline at end of file diff --git a/test/error-cases/missing-base/webpack.config.js b/test/error-cases/missing-base/webpack.config.js new file mode 100644 index 0000000..96f9c2e --- /dev/null +++ b/test/error-cases/missing-base/webpack.config.js @@ -0,0 +1,13 @@ +var SitemapPlugin = require('../../../'); + +module.exports = { + output: { + filename: 'index.js', + path: __dirname + '/actual-output', + libraryTarget: 'umd' + }, + + plugins: [ + new SitemapPlugin(null, ['/', '/about']) + ] +}; diff --git a/test/error-cases/missing-object-path/desc.js b/test/error-cases/missing-object-path/desc.js new file mode 100644 index 0000000..e0b61a8 --- /dev/null +++ b/test/error-cases/missing-object-path/desc.js @@ -0,0 +1 @@ +module.exports = 'Path object is missing path attribute'; diff --git a/test/error-cases/missing-object-path/expected-error.js b/test/error-cases/missing-object-path/expected-error.js new file mode 100644 index 0000000..6cbc74e --- /dev/null +++ b/test/error-cases/missing-object-path/expected-error.js @@ -0,0 +1 @@ +module.exports = 'Error: Path is not a string: [object Object]'; \ No newline at end of file diff --git a/test/error-cases/missing-object-path/webpack.config.js b/test/error-cases/missing-object-path/webpack.config.js new file mode 100644 index 0000000..592b1f8 --- /dev/null +++ b/test/error-cases/missing-object-path/webpack.config.js @@ -0,0 +1,13 @@ +var SitemapPlugin = require('../../../'); + +module.exports = { + output: { + filename: 'index.js', + path: __dirname + '/actual-output', + libraryTarget: 'umd' + }, + + plugins: [ + new SitemapPlugin('https://mysite.com', [{ priority: '1.0' }]) + ] +}; diff --git a/test/index.js b/test/index.js index fde55d4..84e29ce 100644 --- a/test/index.js +++ b/test/index.js @@ -9,12 +9,14 @@ var errorCases = getSubDirsSync(__dirname + '/error-cases'); describe('Success cases', function() { successCases.forEach(function(successCase) { - describe(successCase, function () { + var desc = require('./success-cases/' + successCase + '/desc.js'); + + describe(desc, function () { beforeEach(function (done) { clean(__dirname + '/success-cases/' + successCase + '/actual-output', done); }); - it('generates the expected HTML files', function (done) { + it('generates the expected sitemap', function (done) { var webpackConfig = require('./success-cases/' + successCase + '/webpack.config.js'); webpack(webpackConfig, function(err, stats) { @@ -39,3 +41,26 @@ describe('Success cases', function() { }); }); }); + +describe('Error cases', function() { + errorCases.forEach(function(errorCase) { + var desc = require('./error-cases/' + errorCase + '/desc.js'); + + describe(desc, function () { + beforeEach(function (done) { + clean(__dirname + '/error-cases/' + errorCase + '/actual-output', done); + }); + + it('generates the expected error', function (done) { + var webpackConfig = require('./error-cases/' + errorCase + '/webpack.config.js'); + var expectedError = require('./error-cases/' + errorCase + '/expected-error.js'); + + webpack(webpackConfig, function(err, stats) { + var actualError = stats.compilation.errors[0].toString().split('\n')[0]; + expect(actualError).to.include(expectedError); + done(); + }); + }); + }); + }); +}); diff --git a/test/success-cases/basic/desc.js b/test/success-cases/basic/desc.js new file mode 100644 index 0000000..8259bef --- /dev/null +++ b/test/success-cases/basic/desc.js @@ -0,0 +1 @@ +module.exports = 'Basic sitemap'; diff --git a/test/success-cases/custom-name/desc.js b/test/success-cases/custom-name/desc.js new file mode 100644 index 0000000..e138193 --- /dev/null +++ b/test/success-cases/custom-name/desc.js @@ -0,0 +1 @@ +module.exports = 'Sitemap with custom filename'; diff --git a/test/success-cases/custom-name/webpack.config.js b/test/success-cases/custom-name/webpack.config.js index 304341e..a14f3e2 100644 --- a/test/success-cases/custom-name/webpack.config.js +++ b/test/success-cases/custom-name/webpack.config.js @@ -8,6 +8,8 @@ module.exports = { }, plugins: [ - new SitemapPlugin('https://mysite.com', ['/', '/about'], 'map.xml') + new SitemapPlugin('https://mysite.com', ['/', '/about'], { + fileName: 'map.xml' + }) ] }; diff --git a/test/success-cases/global-opts/desc.js b/test/success-cases/global-opts/desc.js new file mode 100644 index 0000000..41c5379 --- /dev/null +++ b/test/success-cases/global-opts/desc.js @@ -0,0 +1 @@ +module.exports = 'Sitemap with options set globally'; diff --git a/test/success-cases/global-opts/webpack.config.js b/test/success-cases/global-opts/webpack.config.js index daf8e6c..d031793 100644 --- a/test/success-cases/global-opts/webpack.config.js +++ b/test/success-cases/global-opts/webpack.config.js @@ -8,10 +8,11 @@ module.exports = { }, plugins: [ - new SitemapPlugin('https://mysite.com', ['/', '/about'], 'sitemap.xml', { + new SitemapPlugin('https://mysite.com', ['/', '/about'], { + fileName: 'sitemap.xml', lastMod: true, changeFreq: 'monthly', - priority: 0.4, + priority: '0.4' }) ] }; diff --git a/test/success-cases/path-opts/desc.js b/test/success-cases/path-opts/desc.js new file mode 100644 index 0000000..b024bca --- /dev/null +++ b/test/success-cases/path-opts/desc.js @@ -0,0 +1 @@ +module.exports = 'Sitemap with a mix of global and path-specific options'; diff --git a/test/success-cases/path-opts/expected-output/map.xml b/test/success-cases/path-opts/expected-output/map.xml new file mode 100644 index 0000000..0471608 --- /dev/null +++ b/test/success-cases/path-opts/expected-output/map.xml @@ -0,0 +1 @@ +https://mysite.com/2016-01-01daily1.0https://mysite.com/about/0.4https://mysite.com/faq/0.5https://mysite.com/contact/0.5 \ No newline at end of file diff --git a/test/success-cases/path-opts/webpack.config.js b/test/success-cases/path-opts/webpack.config.js new file mode 100644 index 0000000..84bdb1d --- /dev/null +++ b/test/success-cases/path-opts/webpack.config.js @@ -0,0 +1,33 @@ +var SitemapPlugin = require('../../../'); + +var paths = [ + { + path: '/', + lastMod: '2016-01-01', + changeFreq: 'daily', + priority: '1.0', + }, + { + path: '/about/', + priority: '0.4' + }, + { + path: '/faq/' + }, + '/contact/' +]; + +module.exports = { + output: { + filename: 'index.js', + path: __dirname + '/actual-output', + libraryTarget: 'umd' + }, + + plugins: [ + new SitemapPlugin('https://mysite.com', paths, { + fileName: 'map.xml', + priority: '0.5' + }) + ] +}; diff --git a/test/success-cases/uneven-slashes/desc.js b/test/success-cases/uneven-slashes/desc.js new file mode 100644 index 0000000..b67d23e --- /dev/null +++ b/test/success-cases/uneven-slashes/desc.js @@ -0,0 +1 @@ +module.exports = 'Sitemap with uneven slashes in the base URL and paths'; diff --git a/test/success-cases/uneven-slashes/expected-output/sitemap.xml b/test/success-cases/uneven-slashes/expected-output/sitemap.xml new file mode 100644 index 0000000..eec2c21 --- /dev/null +++ b/test/success-cases/uneven-slashes/expected-output/sitemap.xml @@ -0,0 +1 @@ +https://mysite.com/https://mysite.com/about/https://mysite.com/faq/ \ No newline at end of file diff --git a/test/success-cases/uneven-slashes/webpack.config.js b/test/success-cases/uneven-slashes/webpack.config.js new file mode 100644 index 0000000..975f747 --- /dev/null +++ b/test/success-cases/uneven-slashes/webpack.config.js @@ -0,0 +1,13 @@ +var SitemapPlugin = require('../../../'); + +module.exports = { + output: { + filename: 'index.js', + path: __dirname + '/actual-output', + libraryTarget: 'umd' + }, + + plugins: [ + new SitemapPlugin('https://mysite.com/', ['/', 'about/', '/faq/']) + ] +};