我尝试根据以下链接中的说明修改vue-hackernews-2.0以使用Webpack code-splitting feature支持延迟加载的路由:
然而,我遇到了一些问题。当我在浏览器中加载应用程序时,在尝试加载服务器端块时,所有建议的语法变体都会在服务器端触发Module not found
错误。
考虑到router.js
...
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
// INSERT CODE-SPLIT POINT SYNTAXES HERE (they are below)
export default new Router({
mode: 'history',
routes: [{
path: '/',
component: Home
}, {
path: '/foo',
component: Foo
}]
})
所有这些语法变体都引发了Module not found
错误:
变体1:
const Home = () => System.import('./views/Home.vue')
const Foo = () => System.import('./views/Foo.vue')
变体2:
const Home = (resolve) => require(['./views/Home.vue'], resolve)
const Foo = (resolve) => require(['./views/Foo.vue'], resolve)
变体3:
const Home = (resolve) => {
require.ensure(['./views/Home.vue'], () => {
resolve(require('./views/Home.vue'))
})
}
const Foo = (resolve) => {
require.ensure(['./views/Foo.vue'], () => {
resolve(require('./views/Foo.vue'))
})
}
错误信息始终如下:
(注意:此错误改编自我对该问题的一个小型复制,不来自hackernews示例)
Error: Cannot find module './0.server.js'
at Function.Module._resolveFilename (module.js:440:15)
at Function.Module._load (module.js:388:25)
at Module.require (module.js:468:17)
at require (internal/module.js:20:19)
at Function.requireEnsure [as e] (__vue_ssr_bundle__:42:25)
at Home (__vue_ssr_bundle__:152:30)
at /Users/razorbeard/projects/vue-2-ssr/node_modules/vue-router/dist/vue-router.js:1421:19
at iterator (/Users/razorbeard/projects/vue-2-ssr/node_modules/vue-router/dist/vue-router.js:1277:5)
at step (/Users/razorbeard/projects/vue-2-ssr/node_modules/vue-router/dist/vue-router.js:1213:9)
at step (/Users/razorbeard/projects/vue-2-ssr/node_modules/vue-router/dist/vue-router.js:1217:9)
我尝试调整我的代码以使用Server-side react with webpack 2 System.import提供的建议,但这些建议也不起作用。
我读了一篇描述使用Webpack DefinePlugin
构建构建时全局变量的post,它允许我检查代码是在服务器上还是在客户端上运行 - 这个允许我在客户端上进行代码拆分,但只需将所有内容捆绑在服务器上。
在服务器webpack配置中:
{
...
plugins: [
new webpack.DefinePlugin({
BROWSER_BUILD: false
})
]
...
}
在客户端webpack配置中:
{
...
plugins: [
new webpack.DefinePlugin({
BROWSER_BUILD: true
})
]
...
}
然后,在与router.js
文件相同的包装片段中,我使用了这种语法变体:
const Home = BROWSER_BUILD ? () => System.import('./views/Home.vue') : require('./views/Home.vue')
const Foo = BROWSER_BUILD ? () => System.import('./views/Foo.vue') : require('./views/Foo.vue')
这使渲染工作 - 部分。直接导航到浏览器中的应用程序(和相应的路径)服务器呈现正确的UI。点击,vue-router
的客户端逻辑将我带到了正确的用户界面。一切似乎都很糟糕 - 直到我打开DevTools:
如果模块作为路径的子组件延迟加载,也会出现同样的问题:
<template>
<div class="page">
<heading></heading>
</div>
</template>
<script>
const Heading = BROWSER_BUILD ? () => System.import('./Heading.vue') : require('./Heading.vue')
export default {
components: {
Heading
}
}
</script>
我试图在官方的Vue论坛上寻求帮助,但空洞地说:http://forum.vuejs.org/t/2-0-help-needed-with-server-rendered-lazy-routes/906
认为这可能是vue-router
的错误,我在那里打开了一个问题:https://github.com/vuejs/vue-router/issues/820
不幸的是,我无法找到解决方案。
所以,我整理了一个小型仓库,重现了这个问题:https://github.com/declandewet/vue2-ssr-lazy-error
我预感到实际问题可能来自https://www.npmjs.com/package/vue-server-renderer。
我真的坚持这一点并习惯于做出反应是多么容易 - 并且非常感谢任何有关解决方案的帮助/提示/方向!
为方便起见,这是来自复制回购的webpack配置:
import fs from 'fs'
import path from 'path'
import webpack from 'webpack'
import validate from 'webpack2-validator'
import { dependencies } from './package.json'
let babelConfig = JSON.parse(fs.readFileSync('./.babelrc'))
/* turn off modules in es2015 preset to enable tree-shaking
(this is on in babelrc because setting it otherwise causes issues with
this config file) */
babelConfig.presets = babelConfig.presets.map(
(preset) => preset === 'es2015' ? ['es2015', { modules: false }] : preset
)
const babelOpts = {
...babelConfig,
babelrc: false,
cacheDirectory: 'babel_cache'
}
const SHARED_CONFIG = {
devtool: 'source-map',
module: {
loaders: [{
test: /\.vue$/,
loader: 'vue'
}, {
test: /\.js$/,
loader: 'babel',
exclude: 'node_modules',
query: babelOpts
}]
},
resolve: {
modules: [
path.join(__dirname, './src'),
'node_modules'
]
}
}
const SERVER_CONFIG = validate({
...SHARED_CONFIG,
target: 'node',
entry: {
server: './src/server.js',
renderer: './src/renderer.js'
},
output: {
path: path.join(__dirname, './dist'),
filename: '[name].js',
chunkFilename: '[id].server.js',
libraryTarget: 'commonjs2'
},
plugins: [
new webpack.DefinePlugin({
BROWSER_BUILD: false,
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
}),
new webpack.BannerPlugin({
banner: 'require("source-map-support").install();',
raw: true,
entryOnly: false
})
],
externals: Object.keys(dependencies)
})
const CLIENT_CONFIG = validate({
...SHARED_CONFIG,
entry: {
app: './src/client.js',
vendor: ['vue']
},
output: {
path: path.join(__dirname, './dist/assets'),
publicPath: '/',
filename: 'bundle.js'
},
plugins: [
new webpack.DefinePlugin({
BROWSER_BUILD: true,
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: 'vendor.js'
})
]
})
export default [SERVER_CONFIG, CLIENT_CONFIG]
编辑:注意到在React中,我们在客户端使用match
为当前视图获取正确的路由配置,我决定使用{{检查哪些组件匹配1}}并找到了一些有趣的东西:
服务器条目:
app.$router.getMatchedComponents()
导航到主页时,会记录到终端:
import app from './app'
export default (context) => {
// using app.$router instead of importing router itself works
// (not sure why the hacker-news example imports the router module instead...)
app.$router.push(context.url)
const components = app.$router.getMatchedComponents()
console.log('server-side', components)
return Promise.all(components.map((component) => component))
.then(() => app)
}
客户端输入:
server-side [ { __file: '/Users/razorbeard/projects/vue-2-ssr/src/views/Home.vue',
render: [Function],
staticRenderFns: [ [Function] ] } ]
导航到主页时,会记录到devtools控制台:
import app from './app'
const components = app.$router.getMatchedComponents()
console.log('client-side', components)
// kickoff client-side hydration
Promise.all(components.map((component) => Promise.resolve(component)))
.then(() => app.$mount('#app'))
如您所见,客户端没有任何组件匹配。