模塊化

模塊化

  • 演變過程

階段一. 按文件劃分,每個文件就是一個獨立模塊,代碼中調(diào)用全局成員
問題:污染全局 命名沖突 完全依靠約定

階段二: 命名空間 每個模塊只暴露一個全局對象,模塊成員掛到對象下面
問題: 沒有私有空間,模塊成員仍然可以被修改,模塊依賴關(guān)系問題

階段三: 立即執(zhí)行函數(shù)內(nèi)部掛載對象,對象上掛載對外成員,私有成員在函數(shù)內(nèi)部

commonjs標(biāo)準(zhǔn)

  • 一個文件都是一個模塊
  • 每個模塊都有單獨作用域
  • module.exports導(dǎo)出成員
  • require載入模塊
  • 同步模式加載模塊

AMD

ESModule

  • 自動采用嚴(yán)格模式
  • 每個模塊都要單獨作用域
  • 通過cors去請求外部js
  • script標(biāo)簽延遲執(zhí)行
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>ES Module - 模塊的特性</title>
</head>
<body>
  <!-- 通過給 script 添加 type = module 的屬性,就可以以 ES Module 的標(biāo)準(zhǔn)執(zhí)行其中的 JS 代碼了 -->
  <script type="module">
    console.log('this is es module')
  </script>

  <!-- 1. ESM 自動采用嚴(yán)格模式,忽略 'use strict' -->
  <script type="module">
    console.log(this)
  </script>

  <!-- 2. 每個 ES Module 都是運行在單獨的私有作用域中 -->
  <script type="module">
    var foo = 100
    console.log(foo)
  </script>
  <script type="module">
    console.log(foo)
  </script>

  <!-- 3. ESM 是通過 CORS 的方式請求外部 JS 模塊的 -->
  <!-- <script type="module" src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script> -->

  <!-- 4. ESM 的 script 標(biāo)簽會延遲執(zhí)行腳本 -->
  <script defer src="demo.js"></script>
  <p>需要顯示的內(nèi)容</p>
</body>
</html>

export import

  • export { }是固定寫法,不是導(dǎo)出對象的意思
  • export default 后面可以接不同類型變量
  • import { } 對應(yīng)export
  • import name 對應(yīng) export default
  • 對外暴露的成員暴露的是引用關(guān)系
  • 導(dǎo)入的成員是只讀成員
  • import 引入文件名路徑要完成不能省略,相對路徑不能省略,省略會認(rèn)為在加載第三方模塊,可以是絕對路徑 url
  • import {} from './module' 或者 import './module' 執(zhí)行模塊, 而不提取具體成員
  • import * as obj from './module'導(dǎo)入所有模塊,所有模塊存儲在對象中
  • import from不能動態(tài)導(dǎo)入 ,可以使用import()函數(shù)動態(tài)導(dǎo)入,返回一個promise
import('./module').then(module=>{
console.log(module)
})
  • 默認(rèn)導(dǎo)出與export導(dǎo)出同時存在
    import {a,b,c as default} from './modules'
    簡寫 import c, {a,b} from './modules'

  • 導(dǎo)入模塊作為導(dǎo)出成員 export {a,b} from './module'

  • script type="nomodule" //不支持 ES Module的瀏覽器才會執(zhí)行

  • es中可以導(dǎo)入commonjs模塊,作為默認(rèn)導(dǎo)出,反之不可以

webpack

  • npm init初始化package.json文件
  • 使用 cnpm i webpack webpack-cli -D
  • npx webpack 默認(rèn)按照 src/index.js打包
  • 自定義配置 項目下新建 webpack.config.js
  • webpack --mode development/production/none 開發(fā)模式/生產(chǎn)模式打包/原始狀態(tài)打包 也可以在配置文件中設(shè)置 mode屬性
const path = require('path')

module.exports = {
  // 這個屬性有三種取值,分別是 production、development 和 none。
  // 1. 生產(chǎn)模式下,Webpack 會自動優(yōu)化打包結(jié)果;
  // 2. 開發(fā)模式下,Webpack 會自動優(yōu)化打包速度,添加一些調(diào)試過程中的輔助;
  // 3. None 模式下,Webpack 就是運行最原始的打包,不做任何額外處理;
  mode: 'development',
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'dist')
  }
}

資源加載

  • 加載其他資源通過指定對應(yīng)loader來解析對應(yīng)文件
  • module屬性 配置 rules來指定對其他資源加載規(guī)則
  • rules中可以指定多個loader,從后往前執(zhí)行 css-loader把css轉(zhuǎn)換為js模塊,style-loader把對應(yīng)模塊以style標(biāo)簽形式插入html中
var path = require("path")
module.exports = {
    mode:'development',
    entry: './src/main.js',
    output: {
        path: path.resolve(__dirname, "./dist"),
        filename: '.js/bundle.[name].[hash].js'
    },
    module:{
        rules:[
              {
                  test:/.css$/,
                  use:['style-loader','css-loader']
              }
        ]
    }
}

文件資源 loader

  • 加載圖片 cnpm i file-loader -D
  • base64加載資源 cnpm i url-loader -D
  test: /\.(png|jpg|gif)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit:10*1024,//10k一下url-loader處理 以上自動使用file-loader
                            esModule: false
                        }
                    }
 ],

常用loader

  1. 編譯轉(zhuǎn)換類loader ex. css-loader => css轉(zhuǎn)換為js模塊
  2. 文件操作類型loader 把文件輸出到對應(yīng)目錄,同時導(dǎo)出文件的訪問路徑 ex.file-loader
  3. 代碼檢查loader 統(tǒng)一代碼風(fēng)格 eslint-loader

webpack 加載資源的方式

  • 兼容 Es module CommonJs AMD
  • 加載的非js也會觸發(fā)資源加載對應(yīng)的loader ex. css中 @import url img的src
  • html-loader a標(biāo)簽href默認(rèn)不會觸發(fā)資源加載,可以在options只配置
 {
        test: /.html$/,
        use: {
          loader: 'html-loader',
          options: {
            attrs: ['img:src', 'a:href']
          }
        }
      }

webpack工作原理

  • 入口js中通過import/require找到對應(yīng)的文件依賴,分別取解析每一個資源模塊對應(yīng)的依賴,形成一個依賴樹,遞歸資源樹,找到對應(yīng)對應(yīng)資源配置的加載器去加載對應(yīng)模塊,最后把加載結(jié)果放到bundle.js

es6轉(zhuǎn)換

  • cnpm i babel-loader @babel/core @babel/preset-env -D
 {
        test: /.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      },

自定義loader

  • 項目根目錄新建markdown-loader.js
  • loader返回結(jié)果必須是一段js代碼,方便交給下一個loader處理
  • 負責(zé)輸入到輸出的轉(zhuǎn)換 類型管道,同一個資源可以使用多個loader
const marked = require('marked')

module.exports = source => {
  //source接受輸入
  const html = marked(source)
  // 返回 html 字符串   交給下一個 html-oader 處理  
  return html 
}

webpack.config.js配置

  rules: [
      {
        test: /.md$/,
        use: [
          'html-loader',
          './markdown-loader'  //從后往前執(zhí)行
        ]
      }
    ]

plugin

  • 解決除了資源加載之外的自動化工作 ex:清除目錄 壓縮代碼 拷貝靜態(tài)資源到輸出目錄
  • 常用插件 clean-webpack-plugin(清除目錄) html-webpack-plugin(生成html)
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')

module.exports = {
  mode: 'none',
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'dist'),
    // publicPath: 'dist/'
  },
  module: {
    rules: [
      {
        test: /.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      },
      {
        test: /.png$/,
        use: {
          loader: 'url-loader',
          options: {
            limit: 10 * 1024 // 10 KB
          }
        }
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    // 用于生成 index.html
    new HtmlWebpackPlugin({
      title: 'Webpack Plugin Sample',
      meta: {
        viewport: 'width=device-width'
      },
      template: './src/index.html'
    }),
    // 用于生成 about.html
    new HtmlWebpackPlugin({
      filename: 'about.html'
    }),
    new CopyWebpackPlugin([
      // 'public/**'
      'public'
    ])
  ]
}


自定義插件 鉤子機制

  • 通過在聲明周期鉤子中掛載函數(shù)來擴展
class MyPlugin {
  apply (compiler) {
    console.log('MyPlugin 啟動')
   //webpack即將往輸出目錄輸出文件執(zhí)行
    compiler.hooks.emit.tap('MyPlugin', compilation => {
      // compilation => 可以理解為此次打包的上下文
      for (const name in compilation.assets) {
        // console.log(name)
        // console.log(compilation.assets[name].source())
        if (name.endsWith('.js')) {
          const contents = compilation.assets[name].source()
          const withoutComments = contents.replace(/\/\*\*+\*\//g, '')
          compilation.assets[name] = {
            source: () => withoutComments,
            size: () => withoutComments.length
          }
        }
      }
    })
  }
}

監(jiān)視模式運行 webpack --watch

  • 編譯自動刷新 cnpm i webpack-dev-server
  • npx webpack-dev-server --open 自動運行webpack進行打包并啟動開發(fā)服務(wù)器
    此時會監(jiān)聽文件變化自動刷新瀏覽器
    并不會生成dist目錄,打包結(jié)果暫存在內(nèi)存中,http-server從內(nèi)存中讀取資源發(fā)送給瀏覽器解析
  • devServer配置靜態(tài)資源訪問路徑 一般是開發(fā)階段配置靜態(tài)資源訪問路徑,生成上線打包public目錄
 devServer:{
       contentBase:'./public'
    },

代理

 devServer:{
       proxy:{
       '/api':{ 
           target:'https://xxxxx',
            //路徑重寫
            pathRewrite:{
                 '^api':''
              },
           //使用實際請求的地址作為主機名
            changeOrigin:true
        }
      }
    },

sourceMap

  • 解決編寫代碼與運行代碼不一致產(chǎn)生的調(diào)試問題
  • jquery末尾 中 添加 //# sourceMappingURL=jquery-3.4.1.min.map生成源碼
  • webpack 配置 devtool:'source-map'

sourceMap 類型

  • eval模式 找問題代碼是打包過后的模塊代碼,每個模塊轉(zhuǎn)換過后的代碼放在eval函數(shù)中執(zhí)行 執(zhí)行最后通過 //# sourceURL = xx指定源碼地址 定位錯誤出現(xiàn)的文件,不會生成sourceMap文件,構(gòu)建速度最快,但無法直接找到錯誤具體行信息
  • eval-source-map 想必eval生成了sourceMap ,可以查看錯誤信息行列信息
  • cheap-eval-source-map 比eval-source-map輕量 只能看到錯誤行信息,速度更多
  • cheap-module-eval-source-map 于cheap-eval-source-map 想比顯示的是沒有經(jīng)過es6編輯的代碼
  • inline-source-map 把sourceMap以base64嵌入源代碼中
  • hidden-source-map 生成了sourceMap文件,代碼中并沒有引入對應(yīng)map文件
  • nosource-source-map 能看到錯誤的行列信息,但看不到源代碼
  • webpack.config.js可以導(dǎo)出一個數(shù)組,產(chǎn)生多少打包結(jié)果

選擇souceMap

  • 開發(fā)環(huán)境 cheap-module-eval-source-map
  • 生成環(huán)境 none/nosource-source-map
//相同入口可以生成  a.js b.js
module.exports = [
    {
        entry: './src/main.js',
        output: {
            filename: 'a.js'
        }
    },
    {
        entry: './src/main.js',
        output: {
            filename: 'b.js'
        }
    }
]

HMR

  • 模塊熱更新 頁面不刷新情況下,更新模塊
  • 集成在webpack-dev-server中 webpack-dev-sercer --hot開啟或者在配置文件中配置
  • webpack-dev-server hot選項設(shè)置為true plugin中
    new webpack.HotModuleReplacementPlugin()
  • js文件熱更新需要自定義處理模塊替換邏輯,默認(rèn)會自動刷新,樣式文件經(jīng)過loader處理,loader中處理過了熱更新,不需要自定義處理
//注冊對應(yīng)模塊發(fā)生變化的處理函數(shù)
module.hot.accept('./edit',function(){
   處理邏輯
})

hot改為hotOnly 出現(xiàn)錯誤,不會自動刷新

不同環(huán)境下配置

1.配置文件中添加判斷
配置文件導(dǎo)出一個函數(shù) 函數(shù)中返回配置項
接受兩個參數(shù),一個是cli 命名傳遞的參數(shù) 一個是運行傳遞的所有參數(shù)
-- npx webpack --env production 傳遞參數(shù)打包生成環(huán)境

const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')

module.exports = (env, argv) => {

  const config = {
    mode: 'development',
    entry: './src/main.js',
    output: {
      filename: 'js/bundle.js'
    },
    devtool: 'cheap-eval-module-source-map',
    devServer: {
      hot: true,
      contentBase: 'public'
    },
    module: {
      rules: [
        {
          test: /\.css$/,
          use: [
            'style-loader',
            'css-loader'
          ]
        },
        {
          test: /\.(png|jpe?g|gif)$/,
          use: {
            loader: 'file-loader',
            options: {
              outputPath: 'img',
              name: '[name].[ext]'
            }
          }
        }
      ]
    },
    plugins: [
      new HtmlWebpackPlugin({
        title: 'Webpack Tutorial',
        template: './src/index.html'
      }),
      new webpack.HotModuleReplacementPlugin()
    ]
  }

  if (env === 'production') {
    config.mode = 'production'
    config.devtool = false
    config.plugins = [
      ...config.plugins,
      new CleanWebpackPlugin(),
      new CopyWebpackPlugin(['public'])
    ]
  }

  return config
}

2 多配置文件方式

  • webpack.dev.js webpack.common.js webpack.prod.js
  • webpack-merge追加新的配置
  • npx webpack --config 配置文件名 進行打包
    common
const path = require("path")
const webpack = require('webpack')
const merge = require('webpack-merge')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
    devServer:{
       contentBase:'./public'
    },
    entry: './src/main.js',
    output: {
        path: path.resolve(__dirname, "./dist"),
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                loader: 'babel-loader',
                exclude: /node_modules/,
            },
            {
                test: /\.vue$/,
                loader: ['vue-loader'],
                exclude: /node_modules/,
            },

            {
                test: /\.less$/,
                use: ['style-loader', 'css-loader', 'less-loader'],
                exclude: /node_modules/,
            },
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ],
                exclude: /node_modules/,
            },
            {
                test: /\.(png|jpg|gif)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 10 * 1024,//10k一下url-loader處理 以上自動使用file-loader
                            esModule: false
                        }
                    }
                ],
                exclude: /node_modules/,

            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(),
        new VueLoaderPlugin(),
        new htmlWebpackPlugin({
            title: 'spa單頁應(yīng)用',
            url: '',
            filename: 'index.html',
            template: './index.html'
        })
    ],
}

dev

// 開發(fā)環(huán)境配置
const merge = require('webpack-merge') // webpack 合并配置插件 詳細了解==>(https://github.com/survivejs/webpack-merge)
const common = require('./webpack.common.js') // 引入公共模塊配置
const webpack = require('webpack') // 引入webpack
module.exports = merge(common, {
    devtool: 'cheap-module-eval-source-map', 
    devServer: {
        contentBase: './dist'
    },
    mode: 'development',
})

prod

// 配置生產(chǎn)環(huán)境
const merge = require('webpack-merge')
const common = require('./webpack.common.js')
const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); 

module.exports = merge(common, {
    mode: 'production',
    devtool:'none',
    plugins: [
        new UglifyJSPlugin()
        new CopyWebpackPlugin(
            {
                patterns: [
                    {
                        from: path.resolve(__dirname, './public'), //定義要拷貝的源目錄,必填項
                        to: 'public' //定義要拷貝到的目標(biāo)目錄,非必填,不填寫則拷貝到打包的output輸出地址中
                    }

                ]
            }
        ),
    ],
})

DefinePlugin

  • 向全局注入全局變量
const webpack = require('webpack')

module.exports = {
  mode: 'none',
  entry: './src/main.js',
  output: {
    filename: 'bundle.js'
  },
  plugins: [
    new webpack.DefinePlugin({
      // 值要求的是一個代碼片段
      API_BASE_URL: JSON.stringify('https://api.example.com')
    })
  ]
}

tree-shaking

  • 去掉未引用代碼
  • babel-loader使用失效,tree-shaking處理的必須是esModule
    rules: [
      {
        test: /\.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              // 如果 Babel 加載模塊時已經(jīng)轉(zhuǎn)換了 ESM,則會導(dǎo)致 Tree Shaking 失效
              // ['@babel/preset-env', { modules: 'commonjs' }]
              // ['@babel/preset-env', { modules: false }]
              // 也可以使用默認(rèn)配置,也就是 auto,這樣 babel-loader 會自動關(guān)閉 ESM 轉(zhuǎn)換
              ['@babel/preset-env', { modules: 'auto' }]
            ]
          }
        }
      }
    ]
    optimization:{
        usedExports:true,
     // 盡可能合并每一個模塊到一個函數(shù)中
       concatenateModules: true,
        minimize:true //開啟壓縮
  },
副作用 sideEffects
  • 模塊執(zhí)行時除了導(dǎo)出成員之外所做的事情
  • 一般用于npm包標(biāo)記是否有副作用
  • package.json配置 "sideEffects":"false" 標(biāo)識項目代碼沒有副作用
  • 一個組件a.js文件引入所有組件,另一個b.js引入了一個a.js中的一個組件,但由于a.js引入了所有組件,打包就會打包所有組件,為了解決這個問題,采用副作用處理,沒有用到的組件模塊就不會打包
  • 要確定項目的副作用,否則使用該功能會誤刪有作用的代碼
  • package.json 可以設(shè)置哪些文件有副作用
 "sideEffects": [
    "./src/extend.js",
    "*.css"
  ]
optimization: {
    sideEffects: true,   //開啟副作用功能
    // 模塊只導(dǎo)出被使用的成員
    // usedExports: true,
    // 盡可能合并每一個模塊到一個函數(shù)中
    // concatenateModules: true,
    // 壓縮輸出結(jié)果
    // minimize: true,
  }

代碼分割

  • 應(yīng)用比較大時,所有模塊打包到一起,會造成體積很大影響加載速度
  • 分包 按需加載 采用動態(tài)導(dǎo)入或者多入口打包來解決
  1. 多入口打包
  • entry采用對象寫法配置多入口
  • output filename: '[name].bundle.js' //動態(tài)輸出文件名 name最后會被替換為入口文件的名稱
  • HtmlWebpackPlugin 指定chunk來指定輸出html的對應(yīng)打包js
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'none',
  entry: {
    index: './src/index.js',
    album: './src/album.js'
  },
  output: {
    filename: '[name].bundle.js'  //動態(tài)輸出文件名  name最后會被替換為入口文件的名稱
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'Multi Entry',
      template: './src/index.html',
      filename: 'index.html',
      chunks: ['index']
    }),
    new HtmlWebpackPlugin({
      title: 'Multi Entry',
      template: './src/album.html',
      filename: 'album.html',
      chunks: ['album']
    })
  ]
}

提取公共模塊

  optimization: {
    splitChunks: {
      // 自動提取所有公共模塊到單獨 bundle
      chunks: 'all'
    }
  },

按需加載

  • 需要某個模塊才去加載對應(yīng)模塊
  • 采用動態(tài)導(dǎo)入,會被自動分包 import('xx').then(module=>{ })
  • /* webpackChunkName: 'components' */ 給分包起名,默認(rèn)是數(shù)字,如果名字起一樣,就會被打包到一個文件中
  if (hash === '#posts') {
    // mainElement.appendChild(posts())
    import(/* webpackChunkName: 'posts' */'./posts/posts').then(({ default: posts }) => {
      mainElement.appendChild(posts())
    })
  } else if (hash === '#album') {
    // mainElement.appendChild(album())
    import(/* webpackChunkName: 'album' */'./album/album').then(({ default: album }) => {
      mainElement.appendChild(album())
    })
  }

MiniCssExtractPlugin

  • 提取css到單獨文件
  • css文件超過一定體積如150kb,才需要考慮提取文件
  • webpack默認(rèn)壓縮不會壓縮css需要單獨插件optimize-css-assets-webpack-plugin
  • terser-webpack-plugin防止css壓縮覆蓋js壓縮
  • minimizer生成環(huán)境默認(rèn)開啟,下面對應(yīng)插件才會運行
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const TerserWebpackPlugin = require('terser-webpack-plugin')

module.exports = {
  mode: 'none',
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: '[name].bundle.js'
  },
  optimization: {
    minimizer: [
      new TerserWebpackPlugin(),
      new OptimizeCssAssetsWebpackPlugin()  
    ]
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          // 'style-loader', // 將樣式通過 style 標(biāo)簽注入
          MiniCssExtractPlugin.loader,
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'Dynamic import',
      template: './src/index.html',
      filename: 'index.html'
    }),
    new MiniCssExtractPlugin()
  ]
}

文件hash

  • 生產(chǎn)環(huán)境資源文件配置hash防止文件修改緩存
  • output與plugin filename 一般支持hash
  1. [name]-[hash].bound.css 項目級別hash 只要項目任何地方改動,當(dāng)前hash就會發(fā)生改變
  2. [name]-[chunkhash].bound.css 同一路的文件發(fā)生改變,hash才會改變
  • [name]-[contenthash].bound.css 文件hash 文件發(fā)生改變才會觸發(fā)hash改變
  • [name]-[contenthash:8].bound.css :數(shù)字指定hash長度
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容