• vue2 vue-router2 webpack1


    转自:http://www.qinshenxue.com/article/20161106163608.html;

    项目地址:https://github.com/qinshenxue/vue2-vue-router2-webpack

    之前写了一篇如何搭建 vue 1.x 工程的文章《vue vue-router webpack》(以下简称为“上文”),在写上文的时候,vue 2还处于beta版本,现在已经正式发布了,在其发布后不久,我就尝试着按上文的步骤来搭建 vue 2.x 的工程,结论是上文的步骤基本上没什么大的问题,只有部分细节需要调整。为了保证整体步骤的完整性,本文依旧从零开始,以一个新手视角,尽可能详尽且不遗漏的阐述每个步骤,同时又为了精简步骤,上文中的一些无关痛痒的话就省略了,对于没有变化的步骤就直接复制上文。

    注意:2017-03-14 将 vue 升至 2.2.4,本文会和 vue 最新版本(仅限 2.x.x)保持一致,升级所带来的影响点会记录在升级记录章节下。

    初始化工程

    新建工程目录vue2practice,在目录下执行npm init来创建一个package.json,在package.json中先添加以下必备模块:

    "devDependencies": {
        "vue": "^2.1.10",                                   
        "vue-loader": "^10.0.2",    // vue 组件(*.vue)的webpack模块加载器
        "vue-router": "^2.1.3",    // vue 路由插件
        "webpack": "^1.14.0",    // 模块加载器兼打包工具
        "webpack-dev-server": "^1.16.2"    // 轻量的 node.js express 服务器,用于开发调试
    }

    新建目录结构如下,新增的目录及文件先空着,下面的步骤会说明添加什么内容。

    vue2pratice
        |-- package.json
        |-- index.html         // 启动页面
        |-- webpack.config.js  // webpack配置文件
        |-- src
            |-- components  // vue UI 组件目录
            |-- views       // vue页面组件目录
            |-- main.js     // 入口文件
            |-- router.js   // vue-router配置
            |-- app.vue     // 工程首页组件
    

    配置webpack

    webpack 默认读取 webpack.config.js,文件名不能随便改,其中 entry 是必须配置的。

    module.exports = {
        entry: './src/main.js',
        output: {
            path: './dist',
            publicPath: '/dist/',
            filename: 'build.js'
        }
    }

    其中output的各配置项作用如下:

    • path: './dist' 打包后js、css、image等存放的目录;
    • publicPath: '/dist/' 可以不配置,如果不配置则取默认publicPath: '/',在实际项目中,静态资源一般集中放在一个文件夹下,比如static目录,那么这里就应该改成publicPath: '/static/',相应的 index.html 中引用的 JS 也要改成src="/static/build.js",publicPath 可以解释为最终发布的服务器上 build.js 所在的目录,其他静态资源也应当在这个目录下。
    • filename: 'build.js' 打包的js文件名,index.html 引用的 JS 要和这里保持一致。

    配置 webpack-dev-server,只需在package.json添加以下启动命令即可。

    "scripts": {
      "dev": "webpack-dev-server --inline --hot --open"
    }

    各命令参数作用如下:

    • --inline 一共两种模式,默认为iframe模式,inline和iframe模式最明显的区别就是访问路径的不同,iframe模式的访问路径是http://localhost:8080/webpack-dev-server/,实际上iframe模式页面嵌入的<iframe>的地址还是http://localhost:8080/,那岂不是可以直接访问,不禁想问为啥不直接访问呢?因为无论是哪种模式,都是为了做到修改代码后能自动刷新,其中iframe模式是在修改代码后,重新加载iframe,而--inline是刷新浏览器,本质上都是重新全部加载一遍
    • --hot iframe不需要配置,配置了反而不能正常刷新了,所以只能配合--inline使用,作用是开启热替换,修改代码后,浏览器只会重新加载修改的组件代码,不会全部重新加载
    • --open 自动打开浏览器

    配置了server,习惯性的测试下上述配置是否成功,确保后续步骤在一个成功的基石上进行,执行npm install,安装完后执行npm run dev,浏览器会自动打开http://localhost:8080/,能访问(可以在index.html添加内容来确认是否启动成功)则说明上面的配置没问题。

    Vue

    新建页面

    在 views 目录下新建about.vue,不像传统的页面,没有html的结构,其实就是一个vue组件,但是承载着页面的功能,后面访问/about 的就是此文件的内容。

    <template>
        <div>
            这是{{page}}页面
        </div>
    </template>
    <script>
        module.exports = {
            data: function () {
                return {
                    page: 'about'
                }
            }
        }
    </script>

    配置路由

    vue 2.0必须配套使用vue-router 2.0,新版变化还比较大,熟悉1.0的需要再仔细看下2.0文档。新版配置更为简单,不需要先实例化后再调用map来配置(router.map({'path':{component:''}})),改为直接在实例化时传入配置(new VueRouter({ routes: [{path: '', component: ''}]})),因此可以把传入的配置提取到router.js中,方便后续配置,外部调用方式为new VueRouter(require('./router')) 。router.js内容如下:

    module.exports = {
        routes: [
            {
                path: '/about',
                component: require('./views/about.vue')
            }
        ]
    }

    首页

    首页(index.html)只需引入打包后的 js 文件(src和webpack.config的output 配置一致),#app是整个网站的挂载点,简单点说其实整个网站就是一个 vue 的实例,#app就是实例el属性值。

    <body>
    <divid="app"></div>
    <scriptsrc="dist/build.js"></script>
    </body>

    接下来就要配置入口js,这个也是和vue 1.0区别比较大的地方,vue-router 1.0通过VueRouter.start(App,'#app')来初始化整个网站,这种方法在vue-router 2.0已经被废弃,因此需要我们来自己来完成new Vue()。main.js内容如下:

    const Vue = require('vue');
    const VueRouter = require('vue-router');
    const App = require('./app.vue');
    Vue.use(VueRouter);
    const router = new VueRouter(require('./router'))
    new Vue({
        el: '#app',
        router: router,
        render: h => h(App)
    })

    链接路由的不再使用<a v-link="{ path: '/about' }"></a>,改为组件的方式。app.vue内容如下:

    <template>
        <div>
            <div>
                <router-linkto="/about">about</router-link>
            </div>
            <div>
                <router-view></router-view>
            </div>
        </div>
    </template>

    配置loader

    上面添加了 vue 文件,需要在webpack.config.js中添加vue对应的loader,才能将vue文件解析成可执行的代码。

    module: {
            loaders: [
                {
                    test: /.vue$/, loader: 'vue'
                }
            ]
    }

    还记得上文提到过,这时候启动会报错,提示一些依赖的loader未安装,果然:

    不同于上文的是这次只报了一个错,下面会说明原因。在package.json添加:

    {
        "vue-template-compiler":"^2.1.10"
    }

    安装后再次启动就成功了。

    上文报错提示缺少很多vue相关的loader,难道新版不需要了?查看node_modules目录,发现vue-loader所依赖的模块都已经安装了(除了vue-template-compiler是自己添加的)。

    原来是vue-loader中已经将所依赖的模块放到了其目录下的package.json中的dependencies,因此不需要自己去添加了,同时也发现vue-template-complier出现在peerDependencies下,这也解释了为什么第一次启动会报缺少它的原因。如图所示:

    还有在app.vue文件中使用了ES6的语法(比如:const,箭头函数)也不会报缺少babel-loader,这也归功于vue-loader新加的vue-template-es2015-compiler。不过仅仅是部分语法,比如import等语法还是需要安装babel-loader。

    支持添加CSS

    此时如果在about.vue文件中添加CSS代码会提示错误Cannot find module "!!vue-style-loader!css-loader!......,这是因为CSS所依赖的loader未安装,前面vue-loader已经自动将其依赖的vue-style-loader安装好了,但是vue-style-loader依赖的css-loader并没有自动安装,需要我们自己安装。在package.json中添加:

    "css-loader":"^0.26.0"

    安装后我们来验证一下,在about.vue中添加如下样式:

    <styletype="text/css">
        .about {
            color: #f00
        }
    </style>

    运行效果如图所示:

    只在vue文件中使用嵌入式(<style>)来添加CSS代码,是不用在webpack.config中配置loader的,可以理解为没有增加新的文件类型,背后是vue-loader利用vue-style-loader和css-loader将<style>编译成JS代码,如果想引用CSS文件的话,那意味着增加了新的文件类型(*.css),也就是说需要安装并配置文件对应的loader才能被编译成JS代码。CSS文件对应的loader为css-loader,前面已经安装,只需在webpack.config.js中增加如下配置:

    {
        test: /.css$/,
        loader: 'vue-style!css'
    }

    JS中引用CSS文件可以像引入JS模块一样调用require,比如在about.vue中添加require('../css/about.css');,那么about.css的样式就会被添加到页面中,自己可以做下测试。

    支持添加图片等静态资源

    vue模板以及CSS中免不了使用图片,现在如果直接加,又会报找不到模块的错误,静态资源(图片、图标字体、音频、视频、svg文件等)对应的loader为url-loader,loader信息及配置如下:

    {
        "url-loader":"^0.5.7",
        "file-loader":"^0.9.0" // url-loader依赖file-loader
    }
    {
        test: /.(jpe?g|png|gif|svg|mp3)$/,
        loader: "url"
    }

    支持CSS预处理语言

    实际项目中大多会采用less、sass、stylus中的一种预处理语言来组织整个项目的CSS,因此需要添加这些预处理语言对应的loader,各个预处理语言的loader信息如下:

    {
        "less": "^2.3.1",    // less-loader依赖less
        "less-loader": "^2.2.3",
        "node-sass": "^3.4.2",    // sass-loader依赖node-sass
        "sass-loader": "^4.0.2",
        "stylus": "^0.54.5",    // stylus-loader依赖stylus
        "stylus-loader": "^2.4.0" 
    }

    同CSS,如果只在vue文件中使用,则不需要配置loader,实际项目中一般只会采用一种,下面将3中常用的一起测试一下。在about.vue添加测试代码如下:

    <template>
        <divclass="about">
            <divclass="test-less">测试less</div>
            <divclass="test-sass">测试sass</div>
            <divclass="test-stylus">测试stylus</div>
        </div>
    </template>
    <stylelang="less">
        @color: #00f;
        .test-less {
            color: @color;
        }
    </style>
    <stylelang="sass">
        $color: #0ff;
        .test-sass {
            color: $color;
        }
    </style>
    <stylelang="stylus">
        color = #f00;
        .test-stylus {
            color: color;
        }
    </style>

    运行效果如下:

    如果想通过引用文件的方式来加载样式,就必须配置loader,配置如下:

    {
        test: /.less$/,
        loader: "vue-style!css!less"
    },
    {
        test: /.scss/,
        loader: "vue-style!css!sass"
    },
    {
        test: /.styl/,
        loader: "vue-style!css!stylus"
    }

    将about.vue中各种语言<style>节点中的样式放进一个对应文件中,然后在about.vue中引用各个文件如下:

    require('../css/about.less');
    require('../css/about.scss');
    require('../css/about.styl');

    打包发布

    项目打包发布有多种选择,比如用gulp,grunt,这里就采用webpack来打包,webpack打包只需一个命令,即webpack --progress --colors,后面参数可以不要,参数是为了让编译的输出内容带有进度(--progress)和颜色(--colors)。在package.json添加如下命令:

    "scripts": {
      "dev": "webpack-dev-server --inline --hot --open",
      "build":"webpack --progress --colors"
    }

    尝试着执行npm run build(我用的是cnpm),如图:

    执行完后发现多了一个dist目录,里面只有一个build.js文件,新增的目录及文件是由webpack.config.js的output配置指定的。查看build.js,我们发现会发现几个问题:

    • 就加了一个about.vue文件,怎么build.js就233k了,虽然是未压缩版,依旧很吓人,不禁让人怀疑是不是哪里出错了,心想后面加了很多页面岂不是得好几M了。
    • js怎么压缩呢?难道还得构建完后再使用别的插件来压缩js?这么折腾人可不好。
    • 页面中添加的CSS哪里去了?怎么在build.js里面!我可不想把CSS放到js里面,怎么才能把CSS单独输出到一个文件里呢?
    • 页面中引用图片哪里去了?又在build.js里面!怎么做到的,原来是转成DataUrl了,那要是一个几百k的图片,不敢想了……

    以上这些问题使用webpack插件就迎刃而解了。

    使用webpack插件

    什么是webpack插件?这个就类似gulp需要安装所需要的插件一样,webpack不可能做到什么事都会。虽然webpack本身就依赖各种loader,但是有些是还是loader无法完成的,这个时候就需要插件来助一臂之力了。webpack 本身内置了一些常用的插件,还可以通过 npm 安装第三方插件。

    下面依次来介绍哪些插件可以解决上面的问题以及插件的使用方法。

    如何使用插件

    插件的使用一般是在 webpack 的配置信息 plugins 选项中指定。比如:

    var webpack = require('webpack');
    module.exports = {
        plugins: [
            new webpack.BannerPlugin('Hello webpack')
        ]
    }

    压缩JS

    压缩JS用的插件为webpack.optimize.UglifyJsPlugin,uglifyjs应该比较熟悉,使用方法如下:

    new webpack.optimize.UglifyJsPlugin({
        compress: {
            warnings: false
        }
    })

    warinings:false是在删除无用代码的时候,不显示警告,其他同类的配置可以查阅github,下面来执行看看效果。

    虽然报错了,仍然输出了build.js,只是未压缩,上图错误是指在main.js第9行12列符号错误,也就是说不识别箭头函数,为啥运行可以?其实如果你去看运行的代码,会发现ES6的语法并未转换,只是Chrome支持这些语法,如果用import就会报错了。解决方法是增加babel-loader,将ES6语法进行转换,在package.json如下模块并安装:

    {
        "babel-loader": "^6.2.8",
        "babel-core": "^6.18.2",
        "babel-preset-es2015":"6.18.0"
    }

    接着配置webpack.config.js:

    {
        test: /.js$/,
        exclude: /node_modules/,
        loader: 'babel'
    }

    上文在配置babel-loader的时候还加了很多参数,这里为什么没加?经测试这种方式(babel-loader?presets[]=es2015)没有效果,为此我还在github上提了个问题,尤雨溪(vue的作者)回答是query只适用于babel-loader,不适用于vue-loader,应该使用.babelrc。也可以在package.json中配置,两种配置方式如下:

    配置好后再执行命令就不会报错了,压缩后的js大小由原来的233k到现在的90k。

    提取CSS

    提取CSS的插件叫extract-text-webpack-plugin,这个不是webpack自带的,需要自己安装,在package.json中添加:

    {
        "extract-text-webpack-plugin": "^1.0.1"
    }
    

    配置方式如下,原来的style-loader就要去掉了,毕竟我们的目标是将CSS提取出来而不是放在head中。

    var ExtractTextPlugin = require("extract-text-webpack-plugin");
    module.exports = {
        module: {
            loaders: [{
                    test: /.css$/,
                    loader: ExtractTextPlugin.extract("css")
                },
                {
                    test: /.less$/,
                    loader: ExtractTextPlugin.extract("css!less")
                },
                {
                    test: /.scss/,
                    loader: ExtractTextPlugin.extract("css!sass")
                },
                {
                    test: /.styl/,
                    loader: ExtractTextPlugin.extract("css!stylus")
                },
            ]
        },
        plugins: [
            new ExtractTextPlugin("css/style.css")
        ]
    }

    在上面的配置中,样式被提取到了style.css文件,在index.html中添加引用后就生效了。启动发现vue文件中<style>里的样式并未纳入到style.css中,而是继续嵌入式在head中,这是由于vue-loader默认用的是vue-style-loader,想要将vue文件中的样式也提取到文件中,需要在webpack.config添加vue的loaders的配置:

    module.exports = {
        module: {},
        plugins: [],
        vue: {
            loaders: {
                css: ExtractTextPlugin.extract("css"),
                less: ExtractTextPlugin.extract("css!less"),
                scss: ExtractTextPlugin.extract("css!sass"),
                stylus: ExtractTextPlugin.extract("css!stylus")
            }
        }
    }

    现在已经将所有的样式都提取到了一个文件中,但你会发现CSS并未压缩,看来ExtractTextPlugin的职责并不包含压缩。

    PostCSS

    既然上面提到CSS并未压缩,那只能去寻找其他的插件来完成这个工作,PostCSS无疑就是最佳的选择之一,当然PostCSS并不是为压缩CSS而生的,它是一个使用JS插件来转换CSS的工具, 这些插件可以支持使用变量,混入,转换未来的CSS语法,内联图片等操作。

    首先安装PostCSS,webpack使用postcss-loader而不是postcss,在package.json中添加postcss-loader并安装:

    "postcss-loader":"^1.2.0"

    我们的首要目标是压缩CSS,既然PostCSS是依赖插件来完成特定目标的,那么我们需要先找到压缩CSS的插件,查询插件的地址为https://github.com/postcss/postcsshttp://postcss.parts/,最终发现插件cssnano可以完成我们的目标。配置PostCSS主要包括两点:

    1. 在webpack.config中注册PostCSS所需插件,vue要单独指定;
    2. 在CSS先关的loader中添加postcss,包括vue的loaders。

    在package.json中添加cssnano并安装:

    "cssnano":"^3.8.1"

    配置如下:

    module.exports = {
        module: {
            loaders: [
                {
                    test: /.css$/,
                    loader: ExtractTextPlugin.extract("css!postcss")
                },
                {
                    test: /.less$/,
                    loader: ExtractTextPlugin.extract("css!postcss!less")
                },
                {
                    test: /.scss/,
                    loader: ExtractTextPlugin.extract("css!postcss!sass")
                },
                {
                    test: /.styl/,
                    loader: ExtractTextPlugin.extract("css!postcss!stylus")
                }
            ]
        },
        vue: {
            loaders: {
                css: ExtractTextPlugin.extract("css!postcss"),
                less: ExtractTextPlugin.extract("css!postcss!less"),
                scss: ExtractTextPlugin.extract("css!postcss!sass"),
                stylus: ExtractTextPlugin.extract("css!postcss!stylus")
            },
            postcss: [require("cssnano")]
        },
        postcss: [require("cssnano")]
    }

    上面利用cssnano完成了压缩,其实cssnano不止是完成了压缩,还优化了你的CSS代码,比如丢弃重复的样式规则、压缩选择器、合并规则等等,更多特性可以去官网查看。就这一个插件,你可能就感受到了PostCSS的魅力,还有很多插件可以帮助你提升效率,比如autoprefixer可以自动添加浏览器前缀等,只要你想的到,就没有它做不到。

    提取图片

    如果是很小的图片,转成DataUrl放在JS中还是可以接受的,毕竟可以减少请求,大图肯定不适合这种方式。如果你完全不想这么做,或者只想单独把大图提出来,只需通过增加url-loader的参数即可。

    {
        test: /.(jpe?g|png|gif|svg)$/,
        loader: "url",
        query:{
            name:'images/[name].[ext]',
            limit:10000    // 单位:字节
        }
    }
    • name:'images/[name].[ext]' 将符合test正则的图片都存在images目录下,[name].[ext]是文件名模板,更多占位符请参考file-loader文档
    • limit:10000 小于10kb的图片才会被转化成DataUrl,设为0并不意味着所有的图片都不被转换,如果想所有图片都不被转换,建议设为1

    静态资源添加版本号

    对于前后端分离的站点来讲,前端所有资源都是静态的,因此防止浏览器缓存就非常有必要。防止缓存的方法一般有两种,一种是在文件名中添加文件内容的hash值(build.xxxx.js),另外一种方法是给每个http请求加版本号参数(build.js?xxxx)。下面分别说明下两种方法如何配置。

    在上一章节中提到了文件名模板占位符,添加版本号就是利用占位符[hash],而且无论哪种方式都是利用这个占位符做配置。JS、CSS 及 Image 的配置如下:

    // hash可配置选项 [<hashType>:hash:<digestType>:<length>] 即 [哈希算法类型:hash:哈希摘要类型:长度]
    // JS
    filename: 'build.[hash:8].js'    // hash
    filename: 'build.js?[hash:8]'    // query
    // CSS
    new ExtractTextPlugin("css/style.[hash:8].css")    //hash
    new ExtractTextPlugin("css/style.css?[hash:8]")    //query
    // Image
    name: 'images/[name][hash:8].[ext]'    // hash
    name: 'images/[name].[ext]?[hash:8]'    // query

    这里很容易想到一个问题,就是打包后的CSS和JS文件名改变了,那意味着index.html中对应的引用地址也要跟着改变,但是现在关键是不知道文件名被改成啥了?(PS:虽然构建完成后显示了,如下图所示)即使知道新的文件名,每次都要手动去改index.html中引用地址也很不方便,希望最好能在打包完成后自动修改对应的引用地址,就像图片一样,CSS或页面中引用的图片自动改成了最新的文件名。下面将阐述如何通过生成首页来解决这个问题。

    自动生成首页

    自动生成首页需要安装webpack插件html-webpack-plugin,模块信息如下:

    "html-webpack-plugin":"^2.24.1"

    调用插件可以不传配置项,那么index.html会生成到output.path所指定的目录下,生成的页面会引用构建好的CSS和JS文件,但是不会包含<div id="app">。通过配置生成首页的模板(template),可以在模板中添加无法通过配置来完成的内容,还可以设置首页标题(title)、生成的目录及文件名(filename)、favicon图标(favicon)等等,所有配置参见项目github地址

    // 使用默认配置
    new HtmlWebpackPlugin()
    // 自定义配置
    new HtmlWebpackPlugin({
        title:'首页标题',
        filename:'../index.html',
        template:'index.tpl.html',
        favicon:'src/images/favicon.ico'
        ...
    })

    自动生成的首页只能用于发布,webpack-dev-server并不会访问生成后的首页,而且在我们开发的过程中也不需要配置诸如JS及CSS的压缩、添加版本号等,因为这些配置无疑会带来server启动变慢、占用资源等问题,解决这些问题最直接的办法就是将开发和生产得配置文件分开。

    其他插件

    除了上述插件外,还有一些打包时需要用到的插件,列举如下。

    使用 webpack 的 DefinePlugin 来指定生产环境,以便在压缩时可以让 UglifyJS 自动删除代码块内的警告语句。

    new webpack.DefinePlugin({
        'process.env': {
            NODE_ENV: '"production"'
        }
    })

    OccurrenceOrderPlugin 可以根据模块调用次数,给模块分配 ids,常被调用的 ids 分配更短的 id,使得 ids 可预测,降低文件大小。既然能减小打包文件体积,当然要用上。

    new webpack.optimize.OccurrenceOrderPlugin()

    后续

    后面可以做的事还有很多,鉴于本文到这里已经很长了,于是决定将后续的配置另起一篇文章(《vue2 vue-router2 webpack(续)》)来讲解。

    升级记录

    vue 2.2.4

    2017-03-14 将 vue 升级至 2.2.4,步骤无影响,所有升级模块信息如下:

    "vue": "^2.2.4",
    "vue-loader": "^11.1.4",
    "vue-router": "^2.3.0",
    "vue-template-compiler": "^2.2.4",
    "webpack-dev-server": "^1.16.3"

    vue 2.1.10

    2017-01-21将vue升级至2.1.10,无影响点。所有升级模块信息如下:

    "vue": "^2.1.10",    // 原2.1.9
    "vue-template-compiler": "^2.1.10",       // 原2.1.9
    "vue-router": "^2.1.3",       // 原2.1.1

    vue 2.1.9

    2017-01-17将vue升级至2.1.9,无影响点。所有升级模块信息如下:

    "vue": "^2.1.9", // 原2.1.8
    "vue-template-compiler": "^2.1.9" // 原2.1.8

    vue 2.1.8

    2017-01-10将vue升级至2.1.8,无影响点。所有升级模块信息如下:

    "vue": "^2.1.8", // 原2.1.6
    "vue-template-compiler": "^2.1.8" // 原2.1.6

    vue 2.1.6

    2016-12-20 将vue 升级至 2.1.6,无影响点。所有升级模块信息如下:

    "vue": "^2.1.6",    // 原 2.1.4
    "vue-template-compiler": "^2.1.6",    // 原 2.1.4
    "webpack": "^1.14.0",    // 原 1.13.3

    vue 2.1.4

    2016-12-07 将 vue 升级至 2.1.4,无影响点。所有升级模块信息如下:

    "vue-router": "^2.1.1", // 原2.1.0
    "vue-template-compiler": "^2.1.4"   //  原2.1.3

    vue 2.1.3

    2016-12-01 将 vue 升级至 2.1.3,其他模块并未升级,升级后需要安装vue-template-compiler项目才能跑起来

  • 相关阅读:
    Remove menucool tooltip trial version
    笔记:Linux(AWS Redhat)开机启动workman进程(/etc/rc.local必须是755权限)
    workman项目设置开机自启动
    Linux应用之crontab定时任务的设置
    在aws ec2上使用root用户登录
    date_default_timezone_set()问题解决方案(PHP5.3以上的)
    Potatso Lite:[限免]ios 自由上网利器
    5+ App开发入门指南
    Nginx或Apache通过反向代理配置wss服务
    phpstudy安装redis
  • 原文地址:https://www.cnblogs.com/sxz2008/p/6692223.html
Copyright © 2020-2023  润新知