├── .gitignore ├── index.js ├── .npmignore ├── .babelrc ├── src ├── routes-parser.js ├── path-splitter.js ├── sitemap-builder.js ├── paths-filter.js └── index.js ├── types └── index.d.ts ├── README.md ├── package.json └── .eslintrc.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | .idea -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | export { default } from './src'; 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | src/ 3 | index.js 4 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": [ 3 | "node_modules/**/*" 4 | ], 5 | "presets": [ 6 | "env" 7 | ] 8 | } -------------------------------------------------------------------------------- /src/routes-parser.js: -------------------------------------------------------------------------------- 1 | const parseRoutes = (routes = []) => { 2 | return routes; 3 | }; 4 | 5 | export default parseRoutes; 6 | -------------------------------------------------------------------------------- /src/path-splitter.js: -------------------------------------------------------------------------------- 1 | const splitPaths = (paths, size) => paths.map((path, i) => { 2 | return (i % size === 0) ? paths.slice(i, i + size) : null; 3 | }).filter(e => e); 4 | 5 | export default splitPaths; 6 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | 2 | /*export declare class Sitemap { 3 | constructor (options?: any); 4 | 5 | save: any; 6 | }*/ 7 | 8 | declare module 'vue-router-sitemap' { 9 | const Sitemap: any; 10 | export = Sitemap; 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/sitemap-builder.js: -------------------------------------------------------------------------------- 1 | import sitemap from 'sitemap'; 2 | 3 | const buildSitemap = (hostname = 'http://localhost', paths = []) => { 4 | return sitemap.createSitemap({ 5 | hostname, 6 | urls: paths.map(item => ({ url: item.path })), 7 | }); 8 | 9 | }; 10 | 11 | export default buildSitemap; 12 | -------------------------------------------------------------------------------- /src/paths-filter.js: -------------------------------------------------------------------------------- 1 | const prepareParams = (paths = [], rules = []) => { 2 | const isCorrectPaths = Array.isArray(paths); 3 | const isCorrectRules = Array.isArray(rules); 4 | 5 | if (!isCorrectPaths) { 6 | paths = typeof paths === 'string' ? [paths] : []; 7 | } 8 | 9 | if (!isCorrectRules) { 10 | rules = typeof rules === 'string' ? [rules] : []; 11 | } 12 | 13 | return { paths, rules }; 14 | }; 15 | 16 | const filterPaths = (pathsParams = [], rulesParams = [], isValidRules = false) => { 17 | const params = prepareParams(pathsParams, rulesParams); 18 | const { paths, rules } = params; 19 | 20 | return paths.filter((item) => { 21 | const path = item.path.trim(); 22 | 23 | if (!path.length) { 24 | return false; 25 | } 26 | 27 | return rules.some(regex => regex.test(path)) === isValidRules; 28 | }); 29 | }; 30 | 31 | export default filterPaths; 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-router-sitemap 2 | Generate sitemap.xml by vue-router configuration 3 | 4 | ## Install 5 | 6 | `npm i --save vue-router-sitemap` 7 | 8 | ## Example usage 9 | 10 | ```js 11 | // router.js 12 | 13 | import VueRouter from 'vue-router'; 14 | 15 | export const router: VueRouter = new VueRouter( 16 | { 17 | routes: [ 18 | { 19 | path: '/', 20 | name: 'index', 21 | component: Index, 22 | }, 23 | ], 24 | }, 25 | ); 26 | ``` 27 | 28 | ```js 29 | // sitemapMiddleware.js 30 | 31 | import VueRouterSitemap from 'vue-router-sitemap'; 32 | import path from 'path'; 33 | import { router } from 'router'; 34 | 35 | ... 36 | export const sitemapMiddleware = () => { 37 | return (req, res) => { 38 | res.set('Content-Type', 'application/xml'); 39 | 40 | const staticSitemap = path.resolve('dist/static', 'sitemap.xml'); 41 | const filterConfig = { 42 | isValid: false, 43 | rules: [ 44 | /\/example-page/, 45 | /\*/, 46 | ], 47 | }; 48 | 49 | new VueRouterSitemap(router).filterPaths(filterConfig).build('http://example.com').save(staticSitemap); 50 | 51 | return res.sendFile(staticSitemap); 52 | }; 53 | }; 54 | 55 | app.get('/sitemap.xml', sitemapMiddleware()); 56 | ... 57 | ``` 58 | 59 | ## License 60 | MIT 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-router-sitemap", 3 | "version": "0.0.3", 4 | "description": "Module to generate a sitemap for vue-router configuration", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "test": "jest --silent --runInBand", 8 | "test:coverage": "jest --silent --coverage", 9 | "build": "rimraf dist && babel ./src --out-dir=./dist --presets env", 10 | "lint": "eslint --fix --ignore-path .gitignore ." 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/40818419/vue-router-sitemap.git" 15 | }, 16 | "typings": "types/index.d.ts", 17 | "files": [ 18 | "dist/*.js", 19 | "types/*.d.ts" 20 | ], 21 | "keywords": [ 22 | "sitemap", 23 | "vue", 24 | "vue-router", 25 | "router" 26 | ], 27 | "author": "Konstantin Kulinicenko", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/40818419/vue-router-sitemap/issues" 31 | }, 32 | "homepage": "https://github.com/40818419/vue-router-sitemap#readme", 33 | "dependencies": { 34 | "sitemap": "1.13.0" 35 | }, 36 | "devDependencies": { 37 | "babel-cli": "6.26.0", 38 | "babel-eslint": "8.2.2", 39 | "babel-loader": "^7.1.4", 40 | "babel-polyfill": "^6.26.0", 41 | "babel-preset-env": "1.6.1", 42 | "eslint": "4.19.1", 43 | "eslint-plugin-vue": "4.4.0", 44 | "jest": "22.4.3", 45 | "rimraf": "^2.6.2" 46 | }, 47 | "peerDependencies": { 48 | "vue-router": "3.0.1" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | 4 | import sitemap from 'sitemap'; 5 | 6 | import parseRoutes from './routes-parser'; 7 | import buildSitemap from './sitemap-builder'; 8 | import splitPaths from './path-splitter'; 9 | import filterPaths from './paths-filter'; 10 | 11 | class VueRouterSitemap { 12 | constructor(router) { 13 | if (!router) { 14 | throw new Error('Need pass router in module'); 15 | } 16 | 17 | this.paths = parseRoutes(router.options.routes); 18 | 19 | return this; 20 | } 21 | 22 | filterPaths(filterConfig) { 23 | this.paths = filterPaths( 24 | this.paths, 25 | filterConfig.rules, 26 | filterConfig.isValid || false, 27 | ); 28 | 29 | return this; 30 | } 31 | 32 | applyParams(paramsConfig) { 33 | this.paths = applyParams(this.paths, paramsConfig); 34 | return this; 35 | } 36 | 37 | build(hostname = 'http://localhost', { limitCountPaths = 49999 } = {}) { 38 | this.hostname = hostname; 39 | this.splitted = splitPaths(this.paths, limitCountPaths); 40 | this.sitemaps = this.splitted.map(paths => buildSitemap(hostname, paths)); 41 | 42 | return this; 43 | } 44 | 45 | save(dist, publicPath = '/') { 46 | const sitemapPaths = []; 47 | 48 | this.sitemaps.forEach((sitemap, index) => { 49 | const savePath = dist.replace('.xml', `-${index}.xml`); 50 | fs.writeFileSync(savePath, sitemap.toString()); 51 | sitemapPaths.push(this.hostname + publicPath + path.basename(savePath)); 52 | }); 53 | 54 | const sitemapIndex = sitemap.buildSitemapIndex({ 55 | urls: sitemapPaths, 56 | hostname: this.hostname, 57 | }); 58 | fs.writeFileSync(dist, sitemapIndex); 59 | 60 | return this; 61 | } 62 | } 63 | 64 | export default VueRouterSitemap; 65 | 66 | export { default as parseRoutes } from './routes-parser'; 67 | export { default as buildSitemap } from './sitemap-builder'; 68 | export { default as splitPaths } from './path-splitter'; 69 | export { default as filterPaths } from './paths-filter'; 70 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": ["eslint:recommended"], 3 | "parser": 'babel-eslint', 4 | "parserOptions": { 5 | "sourceType": "module" 6 | }, 7 | "rules": { 8 | "accessor-pairs": "error", 9 | "array-bracket-spacing": "off", 10 | "array-callback-return": "error", 11 | "arrow-body-style": "off", 12 | "arrow-parens": "off", 13 | "arrow-spacing": ["error", { 14 | "after": true, 15 | "before": true 16 | }], 17 | "block-scoped-var": "error", 18 | "block-spacing": "error", 19 | "brace-style": ["error", "1tbs"], 20 | "callback-return": "off", 21 | "camelcase": "off", 22 | "comma-dangle": ["error", "only-multiline"], 23 | "comma-spacing": ["error", { 24 | "after": true, 25 | "before": false 26 | }], 27 | "comma-style": ["error", "last"], 28 | "complexity": "error", 29 | "computed-property-spacing": ["error", "never"], 30 | "consistent-return": "off", 31 | "consistent-this": "error", 32 | "curly": "error", 33 | "default-case": "error", 34 | "dot-location": ["error", "property"], 35 | "dot-notation": ["error", { 36 | "allowKeywords": true 37 | }], 38 | "eol-last": "error", 39 | "eqeqeq": "error", 40 | "func-names": "error", 41 | "func-style": "off", 42 | "generator-star-spacing": "error", 43 | "global-require": "off", 44 | "guard-for-in": "error", 45 | "handle-callback-err": "error", 46 | "id-blacklist": "error", 47 | "id-length": "off", 48 | "id-match": "error", 49 | "indent": ["error", 2], 50 | "init-declarations": "off", 51 | "jsx-quotes": ["error", "prefer-double"], 52 | "key-spacing": "off", 53 | "keyword-spacing": ["error", { 54 | "after": true, 55 | "before": true 56 | }], 57 | "linebreak-style": ["error", "unix"], 58 | "lines-around-comment": "error", 59 | "max-depth": "error", 60 | "max-len": "off", 61 | "max-lines": "error", 62 | "max-nested-callbacks": "error", 63 | "max-params": "error", 64 | "max-statements": "off", 65 | "max-statements-per-line": "error", 66 | "new-cap": "off", 67 | "new-parens": "error", 68 | "newline-after-var": "off", 69 | "newline-before-return": "off", 70 | "newline-per-chained-call": "off", 71 | "no-alert": "error", 72 | "no-array-constructor": "error", 73 | "no-bitwise": "error", 74 | "no-caller": "error", 75 | "no-catch-shadow": "error", 76 | "no-confusing-arrow": "error", 77 | "no-continue": "error", 78 | "no-div-regex": "error", 79 | "no-duplicate-imports": "error", 80 | "no-else-return": "off", 81 | "no-empty-function": "off", 82 | "no-eq-null": "error", 83 | "no-eval": "error", 84 | "no-extend-native": "error", 85 | "no-extra-bind": "error", 86 | "no-extra-label": "error", 87 | "no-extra-parens": "off", 88 | "no-floating-decimal": "error", 89 | "no-implicit-coercion": "error", 90 | "no-implicit-globals": "error", 91 | "no-implied-eval": "error", 92 | "no-inline-comments": "off", 93 | "no-invalid-this": "error", 94 | "no-iterator": "error", 95 | "no-label-var": "error", 96 | "no-labels": "error", 97 | "no-lone-blocks": "error", 98 | "no-lonely-if": "error", 99 | "no-loop-func": "error", 100 | "no-magic-numbers": "off", 101 | "no-mixed-operators": "error", 102 | "no-mixed-requires": "error", 103 | "no-multi-spaces": "off", 104 | "no-multi-str": "error", 105 | "no-multiple-empty-lines": "error", 106 | "no-negated-condition": "error", 107 | "no-nested-ternary": "error", 108 | "no-new": "error", 109 | "no-new-func": "error", 110 | "no-new-object": "error", 111 | "no-new-require": "error", 112 | "no-new-wrappers": "error", 113 | "no-octal-escape": "error", 114 | "no-param-reassign": "off", 115 | "no-path-concat": "error", 116 | "no-plusplus": "error", 117 | "no-process-env": "off", 118 | "no-process-exit": "error", 119 | "no-proto": "error", 120 | "no-prototype-builtins": "error", 121 | "no-restricted-globals": "error", 122 | "no-restricted-imports": "error", 123 | "no-restricted-modules": "error", 124 | "no-restricted-syntax": "error", 125 | "no-return-assign": "error", 126 | "no-script-url": "error", 127 | "no-self-compare": "error", 128 | "no-sequences": "error", 129 | "no-shadow": "off", 130 | "no-shadow-restricted-names": "error", 131 | "no-spaced-func": "error", 132 | "no-sync": "error", 133 | "no-throw-literal": "error", 134 | "no-trailing-spaces": "error", 135 | "no-undef-init": "error", 136 | "no-undefined": "error", 137 | "no-underscore-dangle": "off", 138 | "no-unmodified-loop-condition": "error", 139 | "no-unneeded-ternary": "error", 140 | "no-unused-expressions": "error", 141 | "no-unused-vars": ["error", { "vars": "all", "args": "none" }], 142 | "no-use-before-define": "error", 143 | "no-useless-call": "error", 144 | "no-useless-computed-key": "error", 145 | "no-useless-concat": "error", 146 | "no-useless-constructor": "error", 147 | "no-useless-escape": "error", 148 | "no-useless-rename": "error", 149 | "no-var": "error", 150 | "no-void": "error", 151 | "no-warning-comments": "error", 152 | "no-whitespace-before-property": "error", 153 | "no-with": "error", 154 | "object-curly-newline": "off", 155 | "object-curly-spacing": ["error", "always"], 156 | "object-property-newline": ["off"], 157 | "object-shorthand": "error", 158 | "one-var": "off", 159 | "one-var-declaration-per-line": "error", 160 | "operator-assignment": "error", 161 | "operator-linebreak": "error", 162 | "padded-blocks": "off", 163 | "prefer-arrow-callback": "error", 164 | "prefer-const": "error", 165 | "prefer-reflect": "off", 166 | "prefer-rest-params": "error", 167 | "prefer-spread": "error", 168 | "prefer-template": "error", 169 | "quote-props": "off", 170 | "quotes": "off", 171 | "radix": "error", 172 | "require-jsdoc": "off", 173 | "rest-spread-spacing": "error", 174 | "semi": "off", 175 | "semi-spacing": "error", 176 | "sort-imports": "off", 177 | "sort-vars": "off", 178 | "space-before-blocks": "error", 179 | "space-before-function-paren": "off", 180 | "space-in-parens": ["error", "never"], 181 | "space-infix-ops": "error", 182 | "space-unary-ops": "error", 183 | "spaced-comment": ["error", "always"], 184 | "strict": "error", 185 | "template-curly-spacing": ["error", "never"], 186 | "unicode-bom": ["error", "never"], 187 | "valid-jsdoc": "error", 188 | "vars-on-top": "error", 189 | "wrap-iife": "error", 190 | "wrap-regex": "error", 191 | "yield-star-spacing": "error", 192 | "yoda": ["error", "never"] 193 | } 194 | }; 195 | --------------------------------------------------------------------------------