From 21ac52435dd70d24e348ad5df91f40cac232bde5 Mon Sep 17 00:00:00 2001 From: EGOIST <0x142857@gmail.com> Date: Sun, 29 Sep 2019 17:48:10 +0800 Subject: [PATCH] fix: handle dynamic to property for links --- .../vue-renderer/app/components/SaberLink.js | 50 +++++++++++++++++ packages/saber/vue-renderer/app/create-app.js | 15 ++++-- packages/saber/vue-renderer/app/router.js | 1 - .../vue-renderer/app/utils/is-absolute-url.js | 14 +++++ packages/saber/vue-renderer/lib/index.js | 2 + .../template-plugins/__test__/link.test.js | 8 +-- .../vue-renderer/lib/template-plugins/link.js | 54 +++++++++++-------- website/pages/docs/routing.md | 43 +++++++++++---- website/src/components/Header.vue | 4 +- website/src/components/PostList.vue | 2 +- website/src/components/Sidebar.vue | 6 +-- website/src/components/SiteNav.vue | 8 +-- website/src/components/Toc.vue | 6 +-- website/src/layouts/docs.vue | 8 +-- 14 files changed, 163 insertions(+), 58 deletions(-) create mode 100644 packages/saber/vue-renderer/app/components/SaberLink.js create mode 100644 packages/saber/vue-renderer/app/utils/is-absolute-url.js diff --git a/packages/saber/vue-renderer/app/components/SaberLink.js b/packages/saber/vue-renderer/app/components/SaberLink.js new file mode 100644 index 000000000..a31bb9248 --- /dev/null +++ b/packages/saber/vue-renderer/app/components/SaberLink.js @@ -0,0 +1,50 @@ +import isAbsoluteUrl from '../utils/is-absolute-url' + +const setAttribute = (attrs, name, value) => { + if (attrs[name] === undefined) { + attrs[name] = value + } +} + +const HTTP_RE = /^https?:\/\// + +export default { + name: 'SaberLink', + + functional: true, + + render(h, { data, children, parent }) { + const attrs = { ...data.attrs } + const isExternal = typeof attrs.to === 'string' && isAbsoluteUrl(attrs.to) + + if (isExternal) { + if (HTTP_RE.test(attrs.to)) { + setAttribute(attrs, 'rel', 'noopener noreferrer') + + if (attrs.openLinkInNewTab !== false) { + setAttribute(attrs, 'target', '_blank') + } + } + attrs.href = attrs.to + delete attrs.to + } else { + if (typeof attrs.to === 'string') { + attrs.to = parent.$saber.getPageLink(attrs.to) + } else { + const { route } = parent.$router.resolve(attrs.to) + attrs.to = parent.$saber.getPageLink(route.fullPath) + } + } + + delete attrs.openLinkInNewTab + + return h( + isExternal ? 'a' : 'router-link', + { + ...data, + attrs + }, + children + ) + } +} diff --git a/packages/saber/vue-renderer/app/create-app.js b/packages/saber/vue-renderer/app/create-app.js index 1c2cd4ac3..08aa076dc 100644 --- a/packages/saber/vue-renderer/app/create-app.js +++ b/packages/saber/vue-renderer/app/create-app.js @@ -6,6 +6,7 @@ import layouts from '#cache/layouts' import createRouter from './router' import Layout from './components/LayoutManager.vue' import ClientOnly from './components/ClientOnly' +import SaberLink from './components/SaberLink' import extendBrowserApi from '#cache/extend-browser-api' import { join, dirname } from './helpers/path' import injectConfig from './helpers/inject-config' @@ -16,6 +17,7 @@ Vue.config.productionTip = false Vue.component(ClientOnly.name, ClientOnly) Vue.component(Layout.name, Layout) +Vue.component(SaberLink.name, SaberLink) Vue.use(Meta, { keyName: 'head', @@ -122,9 +124,16 @@ export default context => { }, getPageLink(link) { - const matched = Array.isArray(link) - ? link // The link is already parsed - : /^([^#?]+)([#?].*)?$/.exec(link) + if (typeof link !== 'string' && process.env.NODE_ENV !== 'production') { + throw new TypeError(`Expect link to be a string`) + } + + // Already a route path, directly return + if (/^\//.test(link)) { + return link + } + + const matched = /^([^#?]+)([#?].*)?$/.exec(link) if (!matched) { return link diff --git a/packages/saber/vue-renderer/app/router.js b/packages/saber/vue-renderer/app/router.js index 4ebb2957d..bbfb356ea 100644 --- a/packages/saber/vue-renderer/app/router.js +++ b/packages/saber/vue-renderer/app/router.js @@ -6,7 +6,6 @@ import routes from '#cache/routes' Vue.use(Router) // Make `` prefetch-able Vue.use(RoutePrefetch, { - componentName: 'SaberLink', // Only enable prefetching in production mode prefetch: process.env.NODE_ENV === 'production' }) diff --git a/packages/saber/vue-renderer/app/utils/is-absolute-url.js b/packages/saber/vue-renderer/app/utils/is-absolute-url.js new file mode 100644 index 000000000..b78fbe805 --- /dev/null +++ b/packages/saber/vue-renderer/app/utils/is-absolute-url.js @@ -0,0 +1,14 @@ +export default url => { + if (typeof url !== 'string') { + throw new TypeError(`Expected a \`string\`, got \`${typeof url}\``) + } + + // Don't match Windows paths `c:\` + if (/^[a-zA-Z]:\\/.test(url)) { + return false + } + + // Scheme: https://tools.ietf.org/html/rfc3986#section-3.1 + // Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3 + return /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(url) +} diff --git a/packages/saber/vue-renderer/lib/index.js b/packages/saber/vue-renderer/lib/index.js index ab0301285..0482f5bdb 100644 --- a/packages/saber/vue-renderer/lib/index.js +++ b/packages/saber/vue-renderer/lib/index.js @@ -53,6 +53,8 @@ class VueRenderer { prettify: false }, api.webpackUtils.getCacheOptions('vue-loader', { + // Increse `key` to invalid cache + key: 0, type, 'vue-loader': require('vue-loader/package.json').version, 'vue-template-compiler': require('vue-template-compiler/package.json') diff --git a/packages/saber/vue-renderer/lib/template-plugins/__test__/link.test.js b/packages/saber/vue-renderer/lib/template-plugins/__test__/link.test.js index 99f5bd613..5ec462597 100644 --- a/packages/saber/vue-renderer/lib/template-plugins/__test__/link.test.js +++ b/packages/saber/vue-renderer/lib/template-plugins/__test__/link.test.js @@ -17,10 +17,10 @@ test('basic', async () => { foo `) expect(html).toBe(` - foo - foo - foo - foo + foo + foo + foo + foo foo `) }) diff --git a/packages/saber/vue-renderer/lib/template-plugins/link.js b/packages/saber/vue-renderer/lib/template-plugins/link.js index b3d9e1cc3..21d31a1bf 100644 --- a/packages/saber/vue-renderer/lib/template-plugins/link.js +++ b/packages/saber/vue-renderer/lib/template-plugins/link.js @@ -1,4 +1,19 @@ -const { isAbsoluteUrl } = require('saber-utils') +const getAttribute = (node, name) => { + if (node.attrs[name] !== undefined) { + return { value: node.attrs[name], isStatic: true } + } + + return { + value: node.attrs[`:${name}`] || node.attrs[`v-bind:${name}`], + isStatic: false + } +} + +const removeAttribute = (node, name) => { + delete node.attrs[name] + delete node.attrs[`:${name}`] + delete node.attrs[`v-bind:${name}`] +} module.exports = ({ openLinkInNewTab = true } = {}) => tree => { tree.walk(node => { @@ -9,31 +24,24 @@ module.exports = ({ openLinkInNewTab = true } = {}) => tree => { return node } - if (node.tag === 'a' && node.attrs.href) { - if (isAbsoluteUrl(node.attrs.href)) { - // Add attributes for external link - if (/^https?:\/\//.test(node.attrs.href)) { - node.attrs = Object.assign( - { - target: openLinkInNewTab ? '_blank' : undefined, - rel: 'noopener noreferrer' - }, - node.attrs - ) - } + const href = getAttribute(node, 'href') + + if (node.tag === 'a' && href.value) { + node.tag = 'saber-link' + if (href.isStatic) { + node.attrs.to = href.value } else { - // Convert internal `` to `` - node.tag = 'saber-link' - // Resolve link using `getPageLink` - node.attrs[':to'] = `$saber.getPageLink('${node.attrs.href}')` - delete node.attrs.href + node.attrs[':to'] = href.value } - } - // Resolve link using `getPageLink` - if (node.tag === 'saber-link' && node.attrs.to) { - node.attrs[':to'] = `$saber.getPageLink('${node.attrs.to}')` - delete node.attrs.to + removeAttribute(node, 'href') + + if ( + openLinkInNewTab === false && + getAttribute(node, 'openLinkInNewTab').value === undefined + ) { + node.attrs[':openLinkInNewTab'] = JSON.stringify(openLinkInNewTab) + } } return node diff --git a/website/pages/docs/routing.md b/website/pages/docs/routing.md index bfe44ec6b..d11f43993 100644 --- a/website/pages/docs/routing.md +++ b/website/pages/docs/routing.md @@ -3,9 +3,9 @@ title: Routing layout: docs --- -## Client-side transitions with `` +## Client-side transitions with `` element -In Saber, client-side transitions between routes can be enabled via a `` component. It's quite similar to Vue Router's `` component but with more features like page prefetching. +In Saber, client-side transitions between routes can be enabled via `` elements: Basic example, `./pages/index.vue`: @@ -13,21 +13,44 @@ Basic example, `./pages/index.vue`: ``` -Note that when you are using Markdown pages, internal links will be converted to `` automatically, and you can also reference pages using relative path, for example, `./pages/about.md`: +It also works in Markdown since links are transformed to `` elements as well. -```markdown -[Contact us](./contact.md) +Internally, `` elements are converted to a built-in component [``](components.md#saberlink), so these are equivalent: + +```vue +About +About +``` + +`` will be rendered as `` element if the link is an absolute URL (like `https://github.com`), otherwise it's rendered as Vue Router's `` component. + +## Reference local pages + +You can use `` element to reference local pages by filename: + +```vue +About +``` + +..is converted to: + +```html +About ``` -This will be converted to: +This is useful if you're not sure what the permalink is or you might change the permalink format in the future. + +## Disable this with `saber-ignore` + +If you dont' want to use `` for client-side transitions, you can use the `saber-ignore` attribute: ```vue - - Contact us - +Home ``` + +Then this will be rendered as `` instead of ``, it will make the browser fully reload the page. diff --git a/website/src/components/Header.vue b/website/src/components/Header.vue index e881f2936..002a91271 100644 --- a/website/src/components/Header.vue +++ b/website/src/components/Header.vue @@ -17,10 +17,10 @@

- + Saber - +

diff --git a/website/src/components/PostList.vue b/website/src/components/PostList.vue index 2c92b474e..28989064b 100644 --- a/website/src/components/PostList.vue +++ b/website/src/components/PostList.vue @@ -2,7 +2,7 @@

- {{ post.title }} + {{ post.title }}

diff --git a/website/src/components/Sidebar.vue b/website/src/components/Sidebar.vue index 62b561fb7..dc13ded27 100644 --- a/website/src/components/Sidebar.vue +++ b/website/src/components/Sidebar.vue @@ -29,10 +29,10 @@
- {{ childItem.title }} + >{{ childItem.title }}
diff --git a/website/src/components/SiteNav.vue b/website/src/components/SiteNav.vue index 57e11063a..5db891006 100644 --- a/website/src/components/SiteNav.vue +++ b/website/src/components/SiteNav.vue @@ -1,16 +1,16 @@ diff --git a/website/src/layouts/docs.vue b/website/src/layouts/docs.vue index d1609e27d..9bf414663 100644 --- a/website/src/layouts/docs.vue +++ b/website/src/layouts/docs.vue @@ -5,14 +5,14 @@