整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

vue的两种服务器端渲染方案

vue的两种服务器端渲染方案

者:京东零售 姜欣

关于服务器端渲染方案,之前只接触了基于react的Next.js,最近业务开发vue用的比较多,所以调研了一下vue的服务器端渲染方案。 首先:长文预警,下文包括了两种方案的实践,没有耐心的小伙伴可以直接跳到方案标题下,down代码体验一下。

前置知识:

1、什么是服务器端渲染(ssr)?

简单来说就是用户第一次请求页面时,页面上的内容是通过服务器端渲染生成的,浏览器直接显示服务端返回的完整html就可以,加快首屏显示速度。

举个栗子:

当我们访问一个商品列表时,如果使用客户端渲染(csr),浏览器会加载空白的页面,然后下载js文件,通过js在客户端请求数据并渲染页面。如果使用服务器端渲染(ssr),在请求商品列表页面时,服务器会获取所需数据并将渲染后的HTML发送给浏览器,浏览器一步到位直接展示,而不用等待数据加载和渲染,提高用户的首屏体验。

2、服务器端渲染的优缺点

优点:

(1)更好的seo:抓取工具可以直接查看完全渲染的页面。现在比较常用的交互是页面初始展示 loading 菊花图,然后通过异步请求获取内容,但是但抓取工具并不会等待异步完成后再行抓取页面内容。

(2)内容到达更快:不用等待所有的 js 都完成下载并执行,所以用户会更快速地看到完整渲染的页面。

缺点:

(1)服务器渲染应用程序,需要处于 Node.js server 运行环境

(2)开发成本比较高

总结:

总得来说,决定是否使用服务器端渲染,取决于具体的业务场景和需求。对于具有大量静态内容的简单页面,客户端渲染更合适一些,因为它可以更快地加载页面。但是对于需要从服务器动态加载数据的复杂页面,服务器端渲染可能是一个更好的选择,因为他可以提高用户的首屏体验和搜索引擎优化。


下面进入正文

方案一:vue插件vue-server-render

git 示例demo地址

结论前置:不建议用,配置成本高

官网地址: https://v2.ssr.vuejs.org/zh/

首先要吐槽一下官网,按官网教程比较难搞,目录安排的不太合理,一顿操作项目都没起来...

并且官网示例的构建配置代码是webpack4的,现在初始化项目后基本安装的都是webpack5,有一些语法不同

(1)首先,先初始化一个npm项目,然后安装依赖得到一个基础项目 。(此处要注意vue-server-renderer 和 vue 必须匹配版本)

npm init -y
yarn add vue vue-server-renderer -S
yarn add express -S
yarn add webpack webpack-cli friendly-errors-webpack-plugin vue-loader babel-loader @babel/core url-loader file-loader vue-style-loader css-loader sass-loader sass webpack-merge webpack-node-externals -D
yarn add clean-webpack-plugin @babel/preset-env -D
yarn add rimraf // 模拟linx的删除命令,在build时先删除dist
yarn add webpack-dev-middleware webpack-hot-middleware -D
yarn add chokidar  -D //监听变化
yarn add memory-fs -D
yarn add nodemon -D
...实在太多,如有缺失可以在package.json中查找
另外:我现在用的"vue-loader": "^15.9.0"版本,之前用的是"vue-loader": "^17.0.1",报了一个styles的错

(2)配置app.js,entry-client.js,entry-server.js,将官网参考中的示例代码拷贝至对应文件。

app.js

import Vue from 'vue'
import App from './App.vue'
import { createRouter } from './router'
import { createStore } from './store'
import { sync } from 'vuex-router-sync'

// 导出一个工厂函数,用于创建新的
// 应用程序、router 和 store 实例
export function createApp () {
    // 创建 router 和 store 实例
    const router=createRouter()
    const store=createStore()
    
    sync(store, router)

    const app=new Vue({
        router,
        store,
        render: h=> h(App)
    })

    return { app, router, store }
}

entry-client.js

import Vue from 'vue'
import { createApp } from './app'

Vue.mixin({
    beforeMount () {
        const { asyncData }=this.$options
        if (asyncData) {
            this.dataPromise=asyncData({
                store: this.$store,
                route: this.$route
            })
        }
    }
})

const { app, router, store }=createApp()

if (window.__INITIAL_STATE__) {
    store.replaceState(window.__INITIAL_STATE__)
}

router.onReady(()=> {
    // 在初始路由 resolve 后执行,
    // 使用 `router.beforeResolve()`,以便确保所有异步组件都 resolve。
    router.beforeResolve((to, from, next)=> {
        const matched=router.getMatchedComponents(to)
        const prevMatched=router.getMatchedComponents(from)

        // 找出两个匹配列表的差异组件
        let diffed=false
        const activated=matched.filter((c, i)=> {
            return diffed || (diffed=(prevMatched[i] !==c))
        })

        if (!activated.length) {
            return next()
        }

        Promise.all(activated.map(c=> {
            if (c.asyncData) {
                return c.asyncData({ store, route: to })
            }
        })).then(()=> {
            next()
        }).catch(next)
    })

    app.$mount('#app')
})

entry-server.js

import { createApp } from './app'

export default context=> {
    // 返回一个promise,服务器能够等待所有的内容在渲染前,已经准备就绪,
    return new Promise((resolve, reject)=> {
        const { app, router, store }=createApp()
        
        router.push(context.url)

        router.onReady(()=> {
            const matchedComponents=router.getMatchedComponents()
            if (!matchedComponents.length) {
                return reject({ code: 404 })
            }

            // 对所有匹配的路由组件调用 `asyncData()`
            Promise.all(matchedComponents.map(Component=> {
                if (Component.asyncData) {
                    return Component.asyncData({
                        store,
                        route: router.currentRoute
                    })
                }
            })).then(()=> {
                context.state=store.state

                resolve(app)
            }).catch(reject)
        }, reject)
    })
}

(3)在根目录下创建server.js 文件

其中一个非常重要的api:createBundleRenderer,这个api上面有一个方法renderToString将代码转化成html字符串,主要功能就是把用webpack把打包后的服务端代码渲染出来。具体了解可看官网bundle renderer指引。

// server.js
const app=require('express')()
const { createBundleRenderer }=require('vue-server-renderer')
const fs=require('fs')
const path=require('path')
const resolve=file=> path.resolve(__dirname, file)

const isProd=process.env.NODE_ENE==="production"

const createRenderer=(bundle, options)=> {
    return createBundleRenderer(bundle, Object.assign(options, {
        basedir: resolve('./dist'),
        runInNewContext: false,
    }))
}

let renderer, readyPromise
const templatePath=resolve('./src/index.template.html')
if (isProd) {
    const bundle=require('./dist/vue-ssr-server-bundle.json')
    const clientManifest=require('./dist/vue-ssr-client-manifest.json')
    const template=fs.readFileSync(templatePath, 'utf-8')

    renderer=createRenderer(bundle, {
        // 推荐
        template, // (可选)页面模板
        clientManifest // (可选)客户端构建 manifest
    })
} else {
    // 开发模式
    readyPromise=require('./config/setup-dev-server')(app, templatePath, (bundle, options)=> {
        renderer=createRenderer(bundle, options)
    })
}

const render=(req, res)=> {
    const context={
        title: 'hello ssr with webpack',
        meta: `
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
    `,
        url: req.url
    }
    renderer.renderToString(context, (err, html)=> {
        if (err) {
            if (err.code===404) {
                res.status(404).end('Page not found')
            } else {
                res.status(500).end('Internal Server Error')
            }
        } else {
            res.end(html)
        }
    })
}

// 在服务器处理函数中……
app.get('*', isProd ? render : (req, res)=> {
    readyPromise.then(()=> render(req, res))
})

app.listen(8080) // 监听的是8080端口

(4)接下来是config配置

在根目录新增config文件夹,然后新增四个配置文件:webpack.base.config,webpack.client.config,webpack.server.config,setup-dev-server(此方法是一个封装,为了配置个热加载,差点没搞明白,参考了好多)(官网传送门: 构建配置 )

大部分官网有示例代码,但是要在基础上进行一些更改

webpack.base.config

// webpack.base.config
const path=require('path')
// 用来处理后缀为.vue的文件
const { VueLoaderPlugin }=require('vue-loader')
const FriendlyErrorsWebpackPlugin=require('friendly-errors-webpack-plugin')
// 定位到根目录
const resolve=(dir)=> path.join(path.resolve(__dirname, "../"), dir)

// 打包时会先清除一下
// const { CleanWebpackPlugin }=require('clean-webpack-plugin')

const isProd=process.env.NODE_ENV==="production"

module.exports={
    mode: isProd ? 'production' : 'development',
    output: {
        path: resolve('dist'),
        publicPath: '/dist/',
        filename: '[name].[chunk-hash].js'
    },
    resolve: {
        alias: {
            'public': resolve('public')
        }
    },
    module: {
        noParse: /es6-promise\.js$/,
        rules: [
            {
                test: /\.vue$/,
                loader: 'vue-loader',
                options: {
                    compilerOptions: {
                        preserveWhiteSpace: false
                    }
                }
            },
            {
                test: /\.js$/,
                loader: 'babel-loader',
                exclude: /node_modules/
            },
            {
                test: /\.(png|jpg|gif|svg)$/,
                loader: 'url-loader',
                options: {
                    limit: 10000,
                    name: '[name].[ext]?[hash]'
                }
            },
            {
                test: /\.s(a|c)ss?$/,
                use: ['vue-style-loader', 'css-loader', 'sass-loader']
            }
        ]
    },
    performance: {
        hints: false
    },
    plugins:[
        new VueLoaderPlugin(),
        // 编译后的友好提示,比如编译完成或者编译有错误
        new FriendlyErrorsWebpackPlugin(),
        // 打包时会先清除一下
        // new CleanWebpackPlugin()
    ]
}

webpack.client.config

// webpack.client.config
const {merge}=require('webpack-merge')
const baseConfig=require('./webpack.base.config.js')
const VueSSRClientPlugin=require('vue-server-renderer/client-plugin')

module.exports=merge(baseConfig, {
    entry: {
        app: './src/entry-client.js'
    },
    optimization: {
        // 重要信息:这将 webpack 运行时分离到一个引导 chunk 中,
        // 以便可以在之后正确注入异步 chunk。
        // 这也为你的 应用程序/vendor 代码提供了更好的缓存。
        splitChunks: {
            name: "manifest",
            minChunks: Infinity
        }
    },
    plugins: [
        // 此插件在输出目录中
        // 生成 `vue-ssr-client-manifest.json`。
        new VueSSRClientPlugin()
    ]
})

webpack.server.config

// webpack.server.config
const {merge}=require('webpack-merge')
const nodeExternals=require('webpack-node-externals')

// webpack的基础配置,比如sass,less预编译等
const baseConfig=require('./webpack.base.config.js')
const VueSSRServerPlugin=require('vue-server-renderer/server-plugin')

module.exports=merge(baseConfig, {
    // 将 entry 指向应用程序的 server entry 文件
    entry: './src/entry-server.js',

    target: 'node',

    // 对 bundle renderer 提供 source map 支持
    devtool: 'source-map',

    // 此处告知 server bundle 使用 Node 风格导出模块(Node-style exports)
    output: {
        libraryTarget: 'commonjs2'
    },

    // https://webpack.js.org/configuration/externals/#function
    // https://github.com/liady/webpack-node-externals
    // 外置化应用程序依赖模块。可以使服务器构建速度更快,
    // 并生成较小的 bundle 文件。
    externals: nodeExternals({
        // 不要外置化 webpack 需要处理的依赖模块。
        // 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,
        // 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单
        allowlist: /\.css$/
    }),

    // 这是将服务器的整个输出
    // 构建为单个 JSON 文件的插件。
    // 默认文件名为 `vue-ssr-server-bundle.json`
    plugins: [
        new VueSSRServerPlugin()
    ]
})

setup-dev-server:封装createRenderer方法

const webpack=require('webpack')
const fs=require('fs')
const path=require('path')
const chokidar=require('chokidar')
const middleware=require("webpack-dev-middleware")
const HMR=require("webpack-hot-middleware")
const MFS=require('memory-fs')

const clientConfig=require('./webpack.client.config')
const serverConfig=require('./webpack.server.config')

const readFile=(fs, file)=> {
    try {
        return fs.readFileSync(path.join(clientConfig.output.path, file), 'utf8')
    } catch (error) {

    }
}

const setupServer=(app, templatePath, cb)=> {
    let bundle
    let clientManifest
    let template
    let ready
    const readyPromise=new Promise(r=> ready=r)

    template=fs.readFileSync(templatePath, 'utf8')
    const update=()=> {
        if (bundle && clientManifest) {
            // 通知 server 进行渲染
            // 执行 createRenderer -> RenderToString
            ready()
            cb(bundle, {
                template,
                clientManifest
            })
        }
    }
    // webpack -> entry-server -> bundle
    const mfs=new MFS();
    const serverCompiler=webpack(serverConfig);

    serverCompiler.outputFileSystem=mfs;
    serverCompiler.watch({}, (err, stats)=> {
        if (err) throw err
        // 之后读取输出:
        stats=stats.toJson()
        stats.errors.forEach(err=> console.error(err))
        stats.warnings.forEach(err=> console.warn(err))
        if (stats.errors.length) return
        bundle=JSON.parse(readFile(mfs, 'vue-ssr-server-bundle.json'))
        update()
    });

    clientConfig.plugins.push(
        new webpack.HotModuleReplacementPlugin()
    )
    clientConfig.entry.app=['webpack-hot-middleware/client', clientConfig.entry.app]
    clientConfig.output.filename='[name].js'

    const clientCompiler=webpack(clientConfig);

    const devMiddleware=middleware(clientCompiler, {
        noInfo: true, publicPath: clientConfig.output.publicPath, logLevel: 'silent'
    })
    app.use(devMiddleware);

    app.use(HMR(clientCompiler));

    clientCompiler.hooks.done.tap('clientsBuild', stats=> {
        stats=stats.toJson()
        stats.errors.forEach(err=> console.error(err))
        stats.warnings.forEach(err=> console.warn(err))
        if (stats.errors.length) return
        clientManifest=JSON.parse(readFile(
            devMiddleware.fileSystem,
            'vue-ssr-client-manifest.json'
        ))
        update()
    })

    // fs -> templatePath -> template
    chokidar.watch(templatePath).on('change', ()=> {
        template=fs.readFileSync(templatePath, 'utf8')
        console.log('template is updated');
        update()
    })

    return readyPromise
}

module.exports=setupServer

(5)配置搞完了接下来是代码渲染

在src目录下,新增index.template.html文件,将官网中的例子(地址:使用一个页面模板 )复制,并进行一些更改

<html>
<head>
    <!-- 使用双花括号(double-mustache)进行 HTML 转义插值(HTML-escaped interpolation) -->
    <title>{{ title }}</title>

    <!-- 使用三花括号(triple-mustache)进行 HTML 不转义插值(non-HTML-escaped interpolation) -->
    {{{ meta }}}
</head>
<body>
<!--这个是告诉我们在哪里插入正文的内容-->
<!--vue-ssr-outlet-->
</body>
</html>

(6)再搞个store和api模拟一下数据请求

这里介绍一下一个很重要的东西asyncData 预取数据,预取数据是在vue挂载前,所以下文这里用了上下文来获取store而不是this

asyncData: ({ store })=> { return store.dispatch('getDataAction') },

在src下创建api文件夹,并在下面创建data.js文件

// data.js
const getData=()=> new Promise((resolve)=> {
    setTimeout(()=> {
        resolve([
            {
                id: 1,
                item: '测试1'
            },
            {
                id: 2,
                item: '测试2'
            },
        ])
    }, 1000)
})

export {
    getData
}

在src下创建store文件夹,并在下面创建index.js文件

// store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

import { getData } from '../api/data'

export function createStore () {
    return new Vuex.Store({
        state: {
            lists: []
        },
        actions: {
            getDataAction ({ commit }) {
                return getData().then((res)=> {                   
                    commit('setData', res)
                })
            }
        },
        mutations: {
            setData (state, data) {
                state.lists=data
            }
        }
    })
}

(7)编写组件,在src/components文件夹下写两个组件,在app.vue中引用一下,用上刚写的模拟数据

Hello.vue

<template>
  <div>
    这里是测试页面一
    <p>{{item}}</p>
    <router-link to="/hello1">链接到测试页面二</router-link>
  </div>
</template>

<script>
export default {
  asyncData: ({ store })=> {
    return store.dispatch('getDataAction')
  },
  computed: {
    item () {
      return this.$store.state.lists
    }
  }
}
</script>

<style lang="scss" scoped>
</style>

Hello1.vue

<template>
  <div>这里是测试页面二{{item}}</div>
</template>

<script>
export default {
  asyncData: ({ store })=> {
    return store.dispatch('getDataAction')
  },
  computed: {
    item () {
      return this.$store.state.lists
    }
  }
}
</script>

<style lang="scss" scoped>
</style>

(8)配置路由并在app.vue使用路由

router.js

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

export function createRouter () {
    return new Router({
        mode: 'history',
        routes: [
            {
                path: '/hello',
                component: ()=> import('./components/Hello.vue')
            },
            {
                path: '/hello1',
                component: ()=> import('./components/Hello1.vue')
            },
        ]
    })
} 

app.vue

<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>

<script>

export default {
  name: 'App',
}
</script>

<style lang="scss" scoped>
</style>

(9)根目录下创建一个.babelrc,进行配置

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "modules": false
      }
    ]
  ]
}

(10)改写package.json执行命令

"dev": "nodemon server.js",
"build": "rimraf dist && npm run build:client && npm run build:server",
"build:client": "webpack --config config/webpack.client.config.js",
"build:server": "webpack --config config/webpack.server.config.js"

大搞告成,执行一下dev命令,可以通过访问localhost:8080端口看到页面,记得带上路由哦~

执行build命令可看到,最后dist文件下共有三个文件:main.[chunk-hash].js,vue-ssr-client-manifest.json,vue-ssr-server-bundle.json

附上文件整体目录结构


??

方案二:基于vue的nuxt.js通用应用框架

git 示例demo地址

一对比,这个就显得丝滑多了~ 官网地址: nuxt.js

先对比一下两种方案的差别

1.vue初始化虽然有cli,但是nuxt.js的cli更加完备
2.nuxt有更合理的工程化目录,vue过于简洁,比如一些component,api文件夹都是要手动创建的
3.路由配置:传统应用需要自己来配置,nuxt.js自动生成
4.没有统一配置,需手动创建。nuxt.js会生成nuxt.config.js
5.传统不易与管理底层框架逻辑(nuxt支持中间件管理,虽然我还没探索过这里)

显而易见这个上手就快多了,也不需要安装一大堆依赖,如果用了sass需要安装sass和sass-loader,反正我是用了

(1)创建一个项目 可选npm,npx,yarn,具体看官方文档

npm init nuxt-app <project-name>

(2)pages下面创建几个文件

nuxt是通过pages页面形成动态的路由,不用手动配置路由。比如在pages下面新增了个文件about.vue,那么这个页面对应的路由就是/about

其实这个时候运行npm run dev 就可以看到简单的页面了

(3)模拟接口

这里介绍一个插件,可以快速创建一个服务

npm i json-server 

安装完后,在根目录新增db.json文件,模拟几个接口

{
  "post": [{"id": 1, "title": "json-server", "author": "jx"}],
  "comments": [{"id": 1, "body": "some comment", "postId": 1}],
  "profile": {"name": "typicode"}
}

运行命令json-server --watch db.json --port=8000(不加会端口冲突),就可以看到


??

因为是get请求,可以直接点击访问可以看到mock的数据已经返回了


??

(4)页面调用

先配置一下axios,推荐使用nuxt.js封装的axios:"@nuxtjs/axios": "^5.13.6",然后再在nuxt.config.js文件中modules下面配置一下就可以使用了

modules: [  '@nuxtjs/axios'],

随便找个接口调用一下

<template>
  <div>
    <div>
      这里是测试页面一
    </div>
    接口返回数据:{{posts}}
  </div>
</template>

<script>
export default {
  name: 'IndexPage',
  async asyncData({$axios}){
    const result=await $axios.get('http://localhost:8000/post')
    return{
      posts: result.data
    }
  }
}
</script>

刷新下页面就可以看到效果了,这里注意$axios有两个get方法,一个$axios.get还会返回头部等信息,另一个$axios.$get只返回结果


总结:

从页面篇幅上应该也能看到哪个容易上手了,nuxt相对于插件来说限定了文件夹的结构,并通过此预定了一些功能,更好上手。预设了利用vue.js开发服务端渲染所需要的各种配置,并且提供了提供了静态站点,异步数据加载,中间件支持,布局支持等

一篇文章讲解“重定向”,本篇文章讲解“视图-模板渲染”。


因为新版的控制器可以无需继承任何的基础类,因此在控制器中如何使用视图取决于你怎么定义控制器。

渲染模板最常用的是控制器类在继承系统控制器基类(\think\Controller)后调用fetch方法,调用格式:

模板文件的写法支持下面几种:


1. 基本使用

下面是一个最典型的用法,不带任何参数:

①新建Index控制器,并在控制器中新建index方法

注意:

1. 不带任何参数,表示系统会按照默认规则自动定位模板文件,其规则是:

当前模块/view/当前控制器名(小写)/当前操作(小写).html

②在index/view/index/下新建index.html模板文件

预览:


2. 修改模板引擎的view_depr(模板文件名分隔符)设置

①修改模板引擎的view_depr为“_”

位置:配置项文件config.php的template参数下。

注意:

1. 如果有更改模板引擎的view_depr设置(假设'view_depr'=>'_')的话,则上面的自动定位规则变成:

当前模块/view/当前控制器(小写)_当前操作(小写).html

②在index/view下新建index_index.html模板文件

访问Index控制器下的index方法,预览:


3. 如果没有按照模板定义规则来定义模板文件(或者需要调用其他控制器下面的某个模板)

1)没有按照模板定义规则来定义模板文件

【例】在Index控制器的add方法中要调用同控制器下的edit.html模板

①Index控制器的add方法

注意:

1. $this->fetch(‘edit’);表示调用当前控制器的edit模板。

②在index/view/index/下新建edit.html模板(view_depr设置为默认)。

访问add方法,预览:

2)调用其他控制器下的某个模板

【例】在Index控制器的edit方法下,调用News控制器下的add模板。

①Index控制器下的edit方法:

②在index/view/news下新建add.html模板。

访问Index控制器的edit方法,预览:


4. 跨模块渲染模板

【例】在index模块的Index控制器的del方法中,调用admin模块User控制器下的index.html模板。

①index模块的Index控制器的del方法

②admin模块的User控制器下的index.html模板

访问index模块的Index的del方法,预览:

注意:

1. 渲染输出不需要写模板文件的路径和后缀。

2. 这里面的控制器和操作并不一定需要有实际对应的控制器和操作,只是一个目录名称和文件名称而已。

【例2】你的项目里面可能根本没有Public控制器,更没有Public控制器的menu操作,但是一样可以使用。

①在Index控制器中新建pub方法,调用Public控制器的menu模板

②在index/view/public下新建menu.html模板

访问Index控制器的pub方法,预览:

5. 从视图根目录开始读取模板

支持从视图根目录开始读取模板,即从view文件夹下读取。

①在Index控制器中新建viewFloder方法,调用视图根目录下的模板。

②在index/view下新建folder.html模板

访问Index控制器的viewFloder方法,预览:

注意:

1. 如果需要调用视图类(think\View)的其它方法,可以直接使用$this->view对象。

【例】调用视图类的config方法(配置模板引擎),设置模板后缀。

视图类(think\View)的config方法:

默认的模板后缀:

在Index方法中,新建viewTest方法

访问viewTest方法,预览:

注意:

1. 在视图根目录下有folder.html模板,当设置模板后缀为.htm时,那么将不会访问folder.html模板,将会访问folder.htm模板。


6. 自定义模板文件位置

如果你的模板文件位置比较特殊或者需要自定义模板文件的位置,可以采用下面的方式处理。

①在Index控制器中新建Custom方法,调用自定义的模板文件位置

../template/index/是相当于当前项目入口文件的位置。

②在项目下创建template/index文件夹,并新建custom.html模板

预览:

注意:

1. 这种方式需要带模板路径和后缀指定一个完整的模板文件位置,这里的../template/index目录是相对于当前项目入口文件位置。

2. 如果是其他的后缀文件,也支持直接输出,例如:

return $this->fetch('../template/public/menu.tpl');

只要../template/index/menu.tpl是一个实际存在的模板文件。

3. 要注意模板文件位置是相对于应用的入口文件,而不是模板目录。

ThinkPHP5连载为卓象程序员原创,转载请联系卓象程序员

关注卓象程序员,定期发布技术文章

下一篇讲解“视图-助手函数+渲染内容”

年来,随着互联网的高速发展,网页中蕴藏的海量数据成为各行业竞争的关键。而在获取这些数据的过程中,一个重要的技术就是爬虫。结合JavaScript渲染技术,爬虫不仅能够更好地应对动态网页,还能为用户带来更加精准、丰富的信息。下面小编将为您详细介绍这一新兴技术。

一、爬虫与JavaScript渲染的概念

爬虫是一种自动化程序,可以模拟人类对网页进行访问,并从中提取所需信息。而JavaScript渲染则是指网页加载时,浏览器会执行其中的JavaScript代码,生成并显示页面内容。传统的爬虫技术只能抓取静态网页内容,无法获取通过JavaScript动态生成的信息。而结合爬虫和JavaScript渲染技术,则可以解决这一问题。

二、爬虫与JavaScript渲染技术的优势

1.动态网页抓取:传统爬虫只能抓取静态页面,无法获取动态生成的内容。而结合JavaScript渲染技术,爬虫可以模拟浏览器行为,获取完整的网页数据。

2.数据准确性:JavaScript渲染技术可以实时加载和更新数据,爬虫可以及时抓取最新的信息,保证数据的准确性。

3.用户体验优化:通过爬虫和JavaScript渲染技术,网页内容可以更加丰富、多样化,提升用户体验。

4.数据分析与挖掘:爬虫获取到的数据可以用于各种分析和挖掘工作,为企业决策提供参考。

三、爬虫与JavaScript渲染的应用案例

1.电商行业:通过爬虫和JavaScript渲染技术,企业可以实时抓取竞争对手的商品信息、价格变动等数据,从而制定更具竞争力的销售策略。

2.新闻媒体:通过爬虫和JavaScript渲染技术,新闻媒体可以快速抓取各类资讯,并实时更新到自己的平台上,提供给用户最新、全面的新闻内容。

3.社交媒体监测:通过爬虫和JavaScript渲染技术,企业可以实时监测社交媒体上与自身品牌相关的讨论和用户反馈,及时做出回应和调整。

4.数据分析与预测:通过爬虫和JavaScript渲染技术,可以获取大量的数据,进行数据分析和挖掘,为企业决策提供可靠的依据。

四、注意事项与发展趋势

1.合法合规:在使用爬虫技术时,需要遵守相关法律法规,尊重网站的使用规则,避免侵犯他人的权益。

2.技术更新迭代:随着互联网技术的不断发展,爬虫和JavaScript渲染技术也在不断更新迭代中。开发者需要及时了解新技术,并灵活应用于实际项目中。

总结:

爬虫与JavaScript渲染技术的结合为我们带来了更多可能性。无论是从数据获取、用户体验还是商业决策等方面,这一技术都具有重要意义。然而,在应用过程中,我们也要遵守相关规定,保护他人的权益。相信随着技术的不断发展,爬虫与JavaScript渲染将会在更多领域展现出其强大的潜力和价值。