├── renovate.json ├── .eslintrc.js ├── .eslintignore ├── lib ├── logger.js └── module.js ├── commitlint.config.js ├── .gitignore ├── jest.config.js ├── husky.config.js ├── .editorconfig ├── test ├── fixture │ └── nuxt.config.js ├── feed-options.js ├── middleware.test.js ├── module.test.js └── __snapshots__ │ └── module.test.js.snap ├── .github └── workflows │ └── ci.yml ├── LICENSE ├── package.json ├── CHANGELOG.md └── README.md /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@nuxtjs" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: [ 4 | '@nuxtjs' 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Common 2 | node_modules 3 | dist 4 | .nuxt 5 | coverage 6 | 7 | # Plugin 8 | lib/plugin.js 9 | -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | const consola = require('consola') 2 | 3 | module.exports = consola.withScope('nuxt:feed') 4 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | '@commitlint/config-conventional' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | *.log* 5 | .nuxt 6 | .vscode 7 | .DS_Store 8 | coverage 9 | dist 10 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | collectCoverage: true, 4 | collectCoverageFrom: [ 5 | 'lib/**/*.js', 6 | '!lib/plugin.js' 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /husky.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | hooks: { 3 | 'commit-msg': 'commitlint -E HUSKY_GIT_PARAMS', 4 | 'pre-commit': 'yarn lint', 5 | 'pre-push': 'yarn lint' 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 2 6 | indent_style = space 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /test/fixture/nuxt.config.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | const { createFeed } = require('../feed-options') 3 | 4 | module.exports = { 5 | rootDir: resolve(__dirname, '../..'), 6 | buildDir: resolve(__dirname, '.nuxt'), 7 | srcDir: __dirname, 8 | render: { 9 | resourceHints: false 10 | }, 11 | modules: [ 12 | { handler: require('../../') } 13 | ], 14 | feed: [ 15 | createFeed() 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /test/feed-options.js: -------------------------------------------------------------------------------- 1 | const createFeed = (type = 'rss2') => ({ 2 | create (feed) { 3 | feed.options = feedOptions 4 | }, 5 | type 6 | }) 7 | 8 | const feedOptions = { 9 | title: 'Feed Title', 10 | description: 'This is my personal feed!', 11 | id: 'http://example.com/', 12 | link: 'http://example.com/', 13 | image: 'http://example.com/image.png', 14 | favicon: 'http://example.com/favicon.ico', 15 | updated: new Date(Date.UTC(2000, 6, 14)), // optional, default = today 16 | copyright: 'All rights reserved 2013, John Doe', 17 | generator: 'awesome', // optional, default = 'https://github.com/nuxt-community/feed-module' 18 | feedLinks: { 19 | json: 'https://example.com/json', 20 | atom: 'https://example.com/atom' 21 | }, 22 | author: { 23 | name: 'John Doe', 24 | email: 'johndoe@example.com', 25 | link: 'https://example.com/johndoe' 26 | } 27 | } 28 | 29 | module.exports = { 30 | createFeed, 31 | feedOptions 32 | } 33 | -------------------------------------------------------------------------------- /test/middleware.test.js: -------------------------------------------------------------------------------- 1 | jest.mock('async-cache', () => { 2 | return jest.fn().mockImplementation(() => { 3 | return { get: () => { throw new Error('Error on create feed') } } 4 | }) 5 | }) 6 | 7 | const { setup, loadConfig, get } = require('@nuxtjs/module-test-utils') 8 | const logger = require('../lib/logger') 9 | const { createFeed } = require('./feed-options') 10 | 11 | logger.mockTypes(() => jest.fn()) 12 | 13 | describe('middleware', () => { 14 | let nuxt 15 | 16 | beforeAll(async () => { 17 | ({ nuxt } = await setup({ 18 | ...loadConfig(__dirname), 19 | dev: false, 20 | feed: [ 21 | { ...createFeed(), ...{ path: '/feed-error.xml' } } 22 | ] 23 | })) 24 | }, 60000) 25 | 26 | beforeEach(() => { 27 | logger.clear() 28 | }) 29 | 30 | afterAll(async () => { 31 | await nuxt.close() 32 | }) 33 | 34 | test('error on handler', async () => { 35 | await expect(get('/feed-error.xml')).rejects.toMatchObject({ 36 | statusCode: 500 37 | }) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | ci: 13 | runs-on: ${{ matrix.os }} 14 | 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, macos-latest, windows-latest] 18 | node: [10, 12] 19 | 20 | steps: 21 | - uses: actions/setup-node@v1 22 | with: 23 | node-version: ${{ matrix.node }} 24 | 25 | - name: checkout 26 | uses: actions/checkout@master 27 | 28 | - name: cache node_modules 29 | uses: actions/cache@v1 30 | with: 31 | path: node_modules 32 | key: ${{ matrix.os }}-node-v${{ matrix.node }}-deps-${{ hashFiles(format('{0}{1}', github.workspace, '/yarn.lock')) }} 33 | 34 | - name: Install dependencies 35 | if: steps.cache.outputs.cache-hit != 'true' 36 | run: yarn 37 | 38 | - name: Lint 39 | run: yarn lint 40 | 41 | - name: Test 42 | run: yarn test 43 | 44 | - name: Coverage 45 | uses: codecov/codecov-action@v1 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Nuxt Community 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nuxtjs/feed", 3 | "version": "2.0.0", 4 | "description": "Feed module enables everyone to have RSS, Atom and Json.", 5 | "keywords": [ 6 | "nuxtjs", 7 | "nuxt", 8 | "feed", 9 | "blog", 10 | "rss", 11 | "atom", 12 | "json" 13 | ], 14 | "license": "MIT", 15 | "contributors": [ 16 | "Alexander Lichter " 17 | ], 18 | "main": "lib/module.js", 19 | "repository": "nuxt-community/feed-module", 20 | "publishConfig": { 21 | "access": "public" 22 | }, 23 | "scripts": { 24 | "dev": "nuxt test/fixture", 25 | "generate": "nuxt generate test/fixture", 26 | "lint": "eslint --ext .js,.vue lib test", 27 | "test": "yarn lint && jest", 28 | "release": "yarn test && standard-version && git push --follow-tags && npm publish" 29 | }, 30 | "files": [ 31 | "lib" 32 | ], 33 | "dependencies": { 34 | "async-cache": "^1.1.0", 35 | "consola": "^2.12.1", 36 | "feed": "^4.2.0" 37 | }, 38 | "devDependencies": { 39 | "@commitlint/cli": "latest", 40 | "@commitlint/config-conventional": "latest", 41 | "@nuxtjs/eslint-config": "latest", 42 | "@nuxtjs/module-test-utils": "latest", 43 | "eslint": "latest", 44 | "husky": "latest", 45 | "jest": "latest", 46 | "nuxt-edge": "latest", 47 | "standard-version": "latest" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [2.0.0](https://github.com/nuxt-community/feed-module/compare/v1.1.0...v2.0.0) (2020-05-26) 6 | 7 | ### Breaking Changes 8 | 9 | * **generate:** feed output in generate folder ([#43](https://github.com/nuxt-community/feed-module/issues/43)) ([8832f39](https://github.com/nuxt-community/feed-module/commit/8832f39)) 10 | 11 | ### Features 12 | 13 | * generated files log ([#74](https://github.com/nuxt-community/feed-module/issues/74)) ([9aae26c](https://github.com/nuxt-community/feed-module/commit/9aae26c)) 14 | 15 | ### Tests 16 | 17 | * 💍 update rss2 snapshots ([#79](https://github.com/nuxt-community/feed-module/issues/79)) ([d320fb0](https://github.com/nuxt-community/feed-module/commit/d320fb0)) 18 | * refactor tests with module-test-utils ([#80](https://github.com/nuxt-community/feed-module/issues/80)) ([e418c34](https://github.com/nuxt-community/feed-module/commit/e418c34)) 19 | * **module:** explicitly call nuxt.ready() ([#46](https://github.com/nuxt-community/feed-module/issues/46)) ([5ddf6d3](https://github.com/nuxt-community/feed-module/commit/5ddf6d3)) 20 | 21 | 22 | 23 | 24 | # [1.1.0](https://github.com/nuxt-community/feed-module/compare/v1.0.0...v1.1.0) (2019-01-12) 25 | 26 | 27 | ### Features 28 | 29 | * factory object ([#30](https://github.com/nuxt-community/feed-module/issues/30)) ([0a62219](https://github.com/nuxt-community/feed-module/commit/0a62219)) 30 | 31 | 32 | 33 | 34 | # [1.0.0](https://github.com/nuxt-community/feed-module/compare/v0.2.0...v1.0.0) (2019-01-12) 35 | 36 | 37 | ### Bug Fixes 38 | 39 | * log error before fatal ([#26](https://github.com/nuxt-community/feed-module/issues/26)) ([2a120a5](https://github.com/nuxt-community/feed-module/commit/2a120a5)) 40 | * send utf-8 charset as header ([#18](https://github.com/nuxt-community/feed-module/issues/18)) ([a412ee4](https://github.com/nuxt-community/feed-module/commit/a412ee4)) 41 | 42 | 43 | ### Features 44 | 45 | * introduce data property ([23e819e](https://github.com/nuxt-community/feed-module/commit/23e819e)) 46 | 47 | 48 | 49 | 50 | # [0.2.0](https://github.com/nuxt-community/feed-module/compare/v0.1.1...v0.2.0) (2018-10-05) 51 | 52 | 53 | ### Bug Fixes 54 | 55 | * fix typo in package.json ([ff69039](https://github.com/nuxt-community/feed-module/commit/ff69039)) 56 | 57 | 58 | ### Features 59 | 60 | * set default generator to nuxt-feed module ([2cfc35b](https://github.com/nuxt-community/feed-module/commit/2cfc35b)) 61 | 62 | 63 | 64 | 65 | ## [0.1.1](https://github.com/nuxt-community/feed-module/compare/v0.1.0...v0.1.1) (2018-04-20) 66 | 67 | 68 | ### Bug Fixes 69 | 70 | * name of property error in generate ([52a5155](https://github.com/nuxt-community/feed-module/commit/52a5155)) 71 | 72 | 73 | 74 | 75 | # [0.1.0](https://github.com/nuxt-community/feed-module/compare/v0.0.1...v0.1.0) (2018-04-18) 76 | 77 | 78 | ### Features 79 | 80 | * accept a factory function as feed parameter ([740ec07](https://github.com/nuxt-community/feed-module/commit/740ec07)) 81 | 82 | 83 | 84 | 85 | ## 0.0.1 (2018-04-14) 86 | -------------------------------------------------------------------------------- /lib/module.js: -------------------------------------------------------------------------------- 1 | const { join, resolve } = require('path') 2 | const { promisify } = require('util') 3 | const { existsSync, mkdirSync, writeFileSync } = require('fs') 4 | const { dirname } = require('path') 5 | const { Feed } = require('feed') 6 | const AsyncCache = require('async-cache') 7 | const logger = require('./logger') 8 | 9 | const defaults = { 10 | path: '/feed.xml', 11 | async create (feed) {}, 12 | cacheTime: 1000 * 60 * 15 13 | } 14 | 15 | module.exports = async function (moduleOptions) { 16 | const options = [ 17 | ...await parseOptions(this.options.feed), 18 | ...await parseOptions(moduleOptions) 19 | ].map(o => ({ ...defaults, ...o })) 20 | 21 | const feedCache = new AsyncCache({ 22 | load (feedIndex, callback) { 23 | createFeed(options[feedIndex], callback).catch(err => logger.error(err)) 24 | } 25 | }) 26 | 27 | feedCache.get = promisify(feedCache.get) 28 | 29 | options.forEach((feedOptions, index) => { 30 | this.nuxt.hook('generate:done', async () => { 31 | if (index === 0) { 32 | logger.info('Generating feeds') 33 | } 34 | 35 | const xmlGeneratePath = resolve(this.options.rootDir, join(this.options.generate.dir, feedOptions.path)) 36 | const xmlGenerateDirPath = dirname(xmlGeneratePath) 37 | 38 | if (!existsSync(xmlGenerateDirPath)) { 39 | mkdirSync(xmlGenerateDirPath, { recursive: true }) 40 | } 41 | writeFileSync(xmlGeneratePath, await feedCache.get(index)) 42 | 43 | logger.success('Generated', feedOptions.path) 44 | }) 45 | 46 | this.addServerMiddleware({ 47 | path: feedOptions.path, 48 | async handler (req, res, next) { 49 | try { 50 | const xml = await feedCache.get(index) 51 | res.setHeader('Content-Type', resolveContentType(feedOptions.type)) 52 | res.end(xml) 53 | } catch (err) { 54 | next(err) 55 | } 56 | } 57 | }) 58 | }) 59 | } 60 | 61 | async function parseOptions (options) { 62 | // Factory function 63 | if (typeof options === 'function') { 64 | options = await options() 65 | } 66 | 67 | // Factory object 68 | if (!Array.isArray(options)) { 69 | if (options.factory) { 70 | options = await options.factory(options.data) 71 | } 72 | } 73 | 74 | // Check if is empty 75 | if (Object.keys(options).length === 0) { 76 | return [] 77 | } 78 | 79 | // Single feed 80 | if (!Array.isArray(options)) { 81 | options = [options] 82 | } 83 | 84 | return options 85 | } 86 | 87 | function resolveContentType (type) { 88 | const lookup = { 89 | rss2: 'application/rss+xml', 90 | atom1: 'application/atom+xml', 91 | json1: 'application/json' 92 | } 93 | return (lookup.hasOwnProperty(type) ? lookup[type] : 'application/xml') + '; charset=UTF-8' 94 | } 95 | 96 | async function createFeed (feedOptions, callback) { 97 | if (!['rss2', 'json1', 'atom1'].includes(feedOptions.type)) { 98 | logger.fatal(`Could not create Feed ${feedOptions.path} - Unknown feed type`) 99 | return callback(null, '', feedOptions.cacheTime) 100 | } 101 | 102 | const feed = new Feed() 103 | 104 | try { 105 | await feedOptions.create.call(this, feed, feedOptions.data) 106 | feed.options = { 107 | generator: 'https://github.com/nuxt-community/feed-module', 108 | ...feed.options 109 | } 110 | } catch (err) { 111 | logger.error(err) 112 | logger.fatal('Error while executing feed creation function') 113 | 114 | return callback(null, '', feedOptions.cacheTime) 115 | } 116 | 117 | return callback(null, feed[feedOptions.type](), feedOptions.cacheTime) 118 | } 119 | 120 | module.exports.meta = require('../package.json') 121 | -------------------------------------------------------------------------------- /test/module.test.js: -------------------------------------------------------------------------------- 1 | jest.setTimeout(60000) 2 | 3 | const { resolve, join } = require('path') 4 | const { readFileSync } = require('fs') 5 | const { setup, generate, loadConfig, get } = require('@nuxtjs/module-test-utils') 6 | const logger = require('../lib/logger') 7 | const { createFeed, feedOptions } = require('./feed-options') 8 | 9 | const config = loadConfig(__dirname) 10 | config.dev = false 11 | 12 | logger.mockTypes(() => jest.fn()) 13 | 14 | describe('module', () => { 15 | let nuxt 16 | 17 | beforeEach(() => { 18 | logger.clear() 19 | }) 20 | 21 | afterEach(async () => { 22 | if (nuxt) { 23 | await nuxt.close() 24 | } 25 | }) 26 | 27 | test('generate simple rss', async () => { 28 | ({ nuxt } = await generate({ 29 | ...config, 30 | feed: [ 31 | { ...createFeed(), ...{ path: '/feed.xml' } }, 32 | { ...createFeed(), ...{ path: '/feed2.xml' } } 33 | ] 34 | })) 35 | 36 | const filePath = resolve(nuxt.options.rootDir, join(nuxt.options.generate.dir, 'feed.xml')) 37 | expect(readFileSync(filePath, { encoding: 'utf8' })).toMatchSnapshot() 38 | }) 39 | 40 | test('generate simple rss in subdir', async () => { 41 | ({ nuxt } = await generate({ 42 | ...config, 43 | feed: [ 44 | { ...createFeed(), ...{ path: join('/feeds/articles', 'feed.xml') } } 45 | ] 46 | })) 47 | 48 | const filePath = resolve(nuxt.options.rootDir, join(nuxt.options.generate.dir, '/feeds/articles', 'feed.xml')) 49 | expect(readFileSync(filePath, { encoding: 'utf8' })).toMatchSnapshot() 50 | }) 51 | 52 | test('simple rss', async () => { 53 | ({ nuxt } = await setup(config)) 54 | 55 | const html = await get('/feed.xml') 56 | expect(html).toMatchSnapshot() 57 | }) 58 | 59 | test('simple atom', async () => { 60 | ({ nuxt } = await setup({ 61 | ...config, 62 | feed: [ 63 | createFeed('atom1') 64 | ] 65 | })) 66 | 67 | const html = await get('/feed.xml') 68 | expect(html).toMatchSnapshot() 69 | }) 70 | 71 | test('simple json', async () => { 72 | ({ nuxt } = await setup({ 73 | ...config, 74 | feed: [ 75 | createFeed('json1') 76 | ] 77 | })) 78 | 79 | const html = await get('/feed.xml') 80 | expect(html).toMatchSnapshot() 81 | }) 82 | 83 | test('non-latin rss', async () => { 84 | ({ nuxt } = await setup({ 85 | ...config, 86 | feed: [ 87 | { 88 | create (feed) { 89 | feed.options = { 90 | title: 'Популярные новости России и мира', 91 | link: 'http://site.ru/feed.xml', 92 | description: 'Новости России и мира на сайте site.ru', 93 | updated: new Date(Date.UTC(2000, 6, 14)) 94 | } 95 | feed.addContributor({ 96 | name: 'Команда проекта site.ru', 97 | email: 'support@ site.ru', 98 | link: 'http://site.ru/' 99 | }) 100 | }, 101 | type: 'rss2' 102 | } 103 | ] 104 | })) 105 | 106 | const html = await get('/feed.xml') 107 | expect(html).toMatchSnapshot() 108 | }) 109 | 110 | test('object rss', async () => { 111 | ({ nuxt } = await setup({ 112 | ...config, 113 | feed: { 114 | data: { title: 'Feed Title' }, 115 | create (feed, data) { 116 | feedOptions.title = data.title 117 | feed.options = feedOptions 118 | }, 119 | type: 'rss2' 120 | } 121 | })) 122 | 123 | const html = await get('/feed.xml') 124 | expect(html).toMatchSnapshot() 125 | }) 126 | 127 | test('factory rss', async () => { 128 | ({ nuxt } = await setup({ 129 | ...config, 130 | feed: { 131 | data: { title: 'Feed Title' }, 132 | factory: data => ({ 133 | data, 134 | create (feed, { title }) { 135 | feedOptions.title = data.title 136 | feed.options = feedOptions 137 | }, 138 | type: 'rss2' 139 | }) 140 | } 141 | })) 142 | 143 | const html = await get('/feed.xml') 144 | expect(html).toMatchSnapshot() 145 | }) 146 | 147 | test('function rss', async () => { 148 | ({ nuxt } = await setup({ 149 | ...config, 150 | feed: () => createFeed() 151 | })) 152 | 153 | const html = await get('/feed.xml') 154 | expect(html).toMatchSnapshot() 155 | }) 156 | 157 | test('multi rss', async () => { 158 | ({ nuxt } = await setup({ 159 | ...config, 160 | feed: [ 161 | { ...createFeed(), ...{ path: '/feed1.xml' } }, 162 | { ...createFeed(), ...{ path: '/feed2.xml' } } 163 | ] 164 | })) 165 | 166 | const html1 = await get('/feed1.xml') 167 | expect(html1).toMatchSnapshot() 168 | 169 | const html2 = await get('/feed2.xml') 170 | expect(html2).toMatchSnapshot() 171 | }) 172 | 173 | test('no type set', async () => { 174 | ({ nuxt } = await setup({ 175 | ...config, 176 | feed: [ 177 | {} 178 | ] 179 | })) 180 | 181 | const html = await get('/feed.xml') 182 | expect(html).toMatchSnapshot() 183 | 184 | expect(logger.fatal).toHaveBeenCalledWith('Could not create Feed /feed.xml - Unknown feed type') 185 | }) 186 | 187 | test('error on create feed', async () => { 188 | ({ nuxt } = await setup({ 189 | ...config, 190 | feed: [ 191 | { 192 | create (feed) { 193 | throw new Error('Error on create feed') 194 | }, 195 | type: 'rss2' 196 | } 197 | ] 198 | })) 199 | 200 | const html = await get('/feed.xml') 201 | expect(html).toMatchSnapshot() 202 | 203 | expect(logger.error).toHaveBeenCalledWith(new Error('Error on create feed')) 204 | expect(logger.fatal).toHaveBeenCalledWith('Error while executing feed creation function') 205 | }) 206 | }) 207 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Feed module - Everyone deserves RSS, Atom and Json 2 | 3 | [![npm version][npm-version-src]][npm-version-href] 4 | [![npm downloads][npm-downloads-src]][npm-downloads-href] 5 | [![Github Actions CI][github-actions-ci-src]][github-actions-ci-href] 6 | [![Codecov][codecov-src]][codecov-href] 7 | [![License][license-src]][license-href] 8 | 9 | > Feed module enables everyone to have RSS, Atom and Json. 10 | 11 | [📖 **Release Notes**](./CHANGELOG.md) 12 | 13 | ## Features 14 | 15 | - Three different feed types (RSS 2.0, ATOM 1.0 and JSON 1.0) 16 | - As many feeds as you like! 17 | - Completely customizable. Need to fetch data before? No problem! 18 | - Works with **all modes** (yes, even generate!) 19 | - For Nuxt 2.x and higher 20 | 21 | ## Setup 22 | 23 | 1. Add `@nuxtjs/feed` dependency to your project 24 | 25 | ```bash 26 | yarn add @nuxtjs/feed # or npm install @nuxtjs/feed 27 | ``` 28 | 29 | 2. Add `@nuxtjs/feed` to the `modules` section of `nuxt.config.js` 30 | 31 | ```js 32 | export default { 33 | modules: [ 34 | ['@nuxtjs/feed', { 35 | // Your feeds here 36 | }] 37 | ] 38 | } 39 | ``` 40 | 41 | ### Using top level options 42 | 43 | ```js 44 | export default { 45 | modules: [ 46 | '@nuxtjs/feed' 47 | ], 48 | feed: [ 49 | // Your feeds here 50 | ] 51 | } 52 | ``` 53 | 54 | ## Configuration 55 | 56 | So... how to get these feeds working now? 57 | 58 | ### Configuration object overview 59 | 60 | ```js 61 | export default { 62 | feed: [ 63 | // A default feed configuration object 64 | { 65 | path: '/feed.xml', // The route to your feed. 66 | async create(feed) {}, // The create function (see below) 67 | cacheTime: 1000 * 60 * 15, // How long should the feed be cached 68 | type: 'rss2', // Can be: rss2, atom1, json1 69 | data: ['Some additional data'] // Will be passed as 2nd argument to `create` function 70 | } 71 | ] 72 | } 73 | ``` 74 | 75 | ### Feed create function 76 | 77 | Let's take a closer look on the `create` function. This is the API that 78 | actually modifies your upcoming feed. 79 | 80 | A simple create function could look like this: 81 | 82 | ```js 83 | import axios from 'axios' 84 | 85 | // In your `feed` array's object: 86 | async create (feed) { 87 | feed.options = { 88 | title: 'My blog', 89 | link: 'https://lichter.io/feed.xml', 90 | description: 'This is my personal feed!' 91 | } 92 | 93 | const posts = await (axios.get('https://blog-api.lichter.io/posts')).data 94 | posts.forEach(post => { 95 | feed.addItem({ 96 | title: post.title, 97 | id: post.url, 98 | link: post.url, 99 | description: post.description, 100 | content: post.content 101 | }) 102 | }) 103 | 104 | feed.addCategory('Nuxt.js') 105 | 106 | feed.addContributor({ 107 | name: 'Alexander Lichter', 108 | email: 'example@lichter.io', 109 | link: 'https://lichter.io/' 110 | }) 111 | } 112 | ``` 113 | 114 | Feed creation is based on the [feed](https://github.com/jpmonette/feed) package. 115 | Please use it as reference and further documentation for modifying the `feed` object 116 | that is passed to the `create` function. 117 | 118 | Using the `create` function gives you almost unlimited possibilities to customize your feed! 119 | 120 | ### Using a feed factory function 121 | 122 | There is one more thing. Imagine you want to add a feed per blog category, but you don't want 123 | to add every category by hand. 124 | 125 | You can use a `factory function` to solve that problem. Instead of a hardcoded array, you can setup 126 | a function that will be called up on feed generation. The function **must** return an array with all 127 | feeds you want to generate. 128 | 129 | ```js 130 | export default { 131 | feed: async () => { 132 | const posts = (await axios.get('https://blog-api.lichter.io/posts')).data 133 | const tags = (await axios.get('https://blog-api.lichter.io/tags')).data 134 | 135 | return tags.map(t => { 136 | const relevantPosts = posts.filter(/*filter posts somehow*/) 137 | 138 | return { 139 | path: `/${t.slug}.xml`, // The route to your feed. 140 | async create(feed) { 141 | feed.options = { 142 | title: `${t.name} - My blog`, 143 | link: `https://blog.lichter.io/${t.slug}.xml`, 144 | description: `All posts related to ${t.name} of my blog` 145 | } 146 | 147 | relevantPosts.forEach(post => { 148 | feed.addItem({ 149 | title: post.title, 150 | id: post.id, 151 | link: `https://blog.lichter.io/posts/${post.slug}`, 152 | description: post.excerpt, 153 | content: post.text 154 | }) 155 | }) 156 | }, 157 | cacheTime: 1000 * 60 * 15, 158 | type: 'rss2' 159 | } 160 | }) 161 | } 162 | } 163 | ``` 164 | 165 | In case you want to pass in data into the factory function, you can use a *factory object*. 166 | 167 | ```js 168 | export default { 169 | feed: { 170 | data: ['Your data here'], 171 | factory: (dataFromFeedDotData) => {/* your factory function */} 172 | } 173 | } 174 | ``` 175 | 176 | ## Development 177 | 178 | 1. Clone this repository 179 | 2. Install dependencies using `yarn install` or `npm install` 180 | 3. Start development server using `npm run dev` 181 | 182 | ## License 183 | 184 | [MIT License](./LICENSE) 185 | 186 | Copyright (c) - Nuxt Community 187 | 188 | 189 | [npm-version-src]: https://img.shields.io/npm/v/@nuxtjs/feed/latest.svg 190 | [npm-version-href]: https://npmjs.com/package/@nuxtjs/feed 191 | 192 | [npm-downloads-src]: https://img.shields.io/npm/dt/@nuxtjs/feed.svg 193 | [npm-downloads-href]: https://npmjs.com/package/@nuxtjs/feed 194 | 195 | [github-actions-ci-src]: https://github.com/nuxt-community/feed-module/workflows/ci/badge.svg 196 | [github-actions-ci-href]: https://github.com/nuxt-community/feed-module/actions?query=workflow%3Aci 197 | 198 | [codecov-src]: https://img.shields.io/codecov/c/github/nuxt-community/feed-module.svg 199 | [codecov-href]: https://codecov.io/gh/nuxt-community/feed-module 200 | 201 | [license-src]: https://img.shields.io/npm/l/@nuxtjs/feed.svg 202 | [license-href]: https://npmjs.com/package/@nuxtjs/feed 203 | -------------------------------------------------------------------------------- /test/__snapshots__/module.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`module error on create feed 1`] = `""`; 4 | 5 | exports[`module factory rss 1`] = ` 6 | " 7 | 8 | 9 | Feed Title 10 | http://example.com/ 11 | This is my personal feed! 12 | Fri, 14 Jul 2000 00:00:00 GMT 13 | https://validator.w3.org/feed/docs/rss2.html 14 | awesome 15 | 16 | Feed Title 17 | http://example.com/image.png 18 | http://example.com/ 19 | 20 | All rights reserved 2013, John Doe 21 | 22 | " 23 | `; 24 | 25 | exports[`module function rss 1`] = ` 26 | " 27 | 28 | 29 | Feed Title 30 | http://example.com/ 31 | This is my personal feed! 32 | Fri, 14 Jul 2000 00:00:00 GMT 33 | https://validator.w3.org/feed/docs/rss2.html 34 | awesome 35 | 36 | Feed Title 37 | http://example.com/image.png 38 | http://example.com/ 39 | 40 | All rights reserved 2013, John Doe 41 | 42 | " 43 | `; 44 | 45 | exports[`module generate simple rss 1`] = ` 46 | " 47 | 48 | 49 | Feed Title 50 | http://example.com/ 51 | This is my personal feed! 52 | Fri, 14 Jul 2000 00:00:00 GMT 53 | https://validator.w3.org/feed/docs/rss2.html 54 | awesome 55 | 56 | Feed Title 57 | http://example.com/image.png 58 | http://example.com/ 59 | 60 | All rights reserved 2013, John Doe 61 | 62 | " 63 | `; 64 | 65 | exports[`module generate simple rss in subdir 1`] = ` 66 | " 67 | 68 | 69 | Feed Title 70 | http://example.com/ 71 | This is my personal feed! 72 | Fri, 14 Jul 2000 00:00:00 GMT 73 | https://validator.w3.org/feed/docs/rss2.html 74 | awesome 75 | 76 | Feed Title 77 | http://example.com/image.png 78 | http://example.com/ 79 | 80 | All rights reserved 2013, John Doe 81 | 82 | " 83 | `; 84 | 85 | exports[`module multi rss 1`] = ` 86 | " 87 | 88 | 89 | Feed Title 90 | http://example.com/ 91 | This is my personal feed! 92 | Fri, 14 Jul 2000 00:00:00 GMT 93 | https://validator.w3.org/feed/docs/rss2.html 94 | awesome 95 | 96 | Feed Title 97 | http://example.com/image.png 98 | http://example.com/ 99 | 100 | All rights reserved 2013, John Doe 101 | 102 | " 103 | `; 104 | 105 | exports[`module multi rss 2`] = ` 106 | " 107 | 108 | 109 | Feed Title 110 | http://example.com/ 111 | This is my personal feed! 112 | Fri, 14 Jul 2000 00:00:00 GMT 113 | https://validator.w3.org/feed/docs/rss2.html 114 | awesome 115 | 116 | Feed Title 117 | http://example.com/image.png 118 | http://example.com/ 119 | 120 | All rights reserved 2013, John Doe 121 | 122 | " 123 | `; 124 | 125 | exports[`module no type set 1`] = `""`; 126 | 127 | exports[`module non-latin rss 1`] = ` 128 | " 129 | 130 | 131 | Популярные новости России и мира 132 | http://site.ru/feed.xml 133 | Новости России и мира на сайте site.ru 134 | Fri, 14 Jul 2000 00:00:00 GMT 135 | https://validator.w3.org/feed/docs/rss2.html 136 | https://github.com/nuxt-community/feed-module 137 | 138 | " 139 | `; 140 | 141 | exports[`module object rss 1`] = ` 142 | " 143 | 144 | 145 | Feed Title 146 | http://example.com/ 147 | This is my personal feed! 148 | Fri, 14 Jul 2000 00:00:00 GMT 149 | https://validator.w3.org/feed/docs/rss2.html 150 | awesome 151 | 152 | Feed Title 153 | http://example.com/image.png 154 | http://example.com/ 155 | 156 | All rights reserved 2013, John Doe 157 | 158 | " 159 | `; 160 | 161 | exports[`module simple atom 1`] = ` 162 | " 163 | 164 | http://example.com/ 165 | Feed Title 166 | 2000-07-14T00:00:00.000Z 167 | awesome 168 | 169 | John Doe 170 | johndoe@example.com 171 | https://example.com/johndoe 172 | 173 | 174 | 175 | This is my personal feed! 176 | http://example.com/image.png 177 | http://example.com/favicon.ico 178 | All rights reserved 2013, John Doe 179 | " 180 | `; 181 | 182 | exports[`module simple json 1`] = ` 183 | "{ 184 | \\"version\\": \\"https://jsonfeed.org/version/1\\", 185 | \\"title\\": \\"Feed Title\\", 186 | \\"home_page_url\\": \\"http://example.com/\\", 187 | \\"feed_url\\": \\"https://example.com/json\\", 188 | \\"description\\": \\"This is my personal feed!\\", 189 | \\"icon\\": \\"http://example.com/image.png\\", 190 | \\"author\\": { 191 | \\"name\\": \\"John Doe\\", 192 | \\"url\\": \\"https://example.com/johndoe\\" 193 | }, 194 | \\"items\\": [] 195 | }" 196 | `; 197 | 198 | exports[`module simple rss 1`] = ` 199 | " 200 | 201 | 202 | Feed Title 203 | http://example.com/ 204 | This is my personal feed! 205 | Fri, 14 Jul 2000 00:00:00 GMT 206 | https://validator.w3.org/feed/docs/rss2.html 207 | awesome 208 | 209 | Feed Title 210 | http://example.com/image.png 211 | http://example.com/ 212 | 213 | All rights reserved 2013, John Doe 214 | 215 | " 216 | `; 217 | --------------------------------------------------------------------------------