从vue-cli中看node.js和webpack的运作

来源:互联网 发布:淘宝客分佣app制作 编辑:程序博客网 时间:2024/05/18 19:19

前言

vue-cli和webpack结合的脚手架挺好用的,但是初次使用对于其中的配置和npm包的引用总是会一脸懵逼,这篇文章是对其中一些相关模块的简单分析。

主要目的是加深我自己对webpack和node.js的认知。

正文

1.项目结构

vue-cli的配置文件主要在build和config文件夹中,其中config文件夹主要是放一些环境变量,webpack的路径等等一些参数。

| -- build    | -- build.js    | -- check-version.js    | -- dev-client.js    | -- dev-server.js    | -- utils.js    | -- vue-loader.conf.js    | -- webpack.base.conf.js    | -- webpack.dev.conf.js    | -- webpack.prod.conf.js    | -- webpack.test.conf.js| -- config    | -- dev.env.js    | -- index.js    | -- prod.env.js    | -- test.env.js......

2.从npm run dev开始

本地调试一般需要热重载,创建本地服务器,所以我们常使用的命令是npm run dev,根据package.json得知实际上执行的是node build/dev-server.js命令。

dev-server.js

好吧,让我们看看dev-server.js到底做了什么。

说实话做的就是,利用express创建一个服务,监听特定的端口,利用express().use()使用wepack的中间件。可以改一改代理,是从本地localhost获取数据改为从其他代理地址获取数据。

// 这是一个版本检测脚本,主要是测试本地的npm和node版本是否要求require('./check-versions')()// ./check-versions.js// 引入三个包,chalk(给字符串在命令行添加颜色)、semver(处理版本号字符串),child_process(添加个子进程)var chalk = require('chalk')var semver = require('semver')// 引入package.json中的node和npm要求版本var packageConfig = require('../package.json')// child_process.execSync(cmd) 相同于同步在命令行执行cmd命令function exec (cmd) {  return require('child_process').execSync(cmd).toString().trim()}// 把信息都放入对象var versionRequirements = [  {    name: 'node',    currentVersion: semver.clean(process.version),    versionRequirement: packageConfig.engines.node  },  {    name: 'npm',    currentVersion: exec('npm --version'),    versionRequirement: packageConfig.engines.npm  }]// 输出一个函数module.exports = function () {  var warnings = []  for (var i = 0; i < versionRequirements.length; i++) {    var mod = versionRequirements[i]    // semver工具比较版本是否符合    if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {    // 不符合,则添加到warning数组      warnings.push(mod.name + ': ' +        chalk.red(mod.currentVersion) + ' should be ' +        chalk.green(mod.versionRequirement)      )    }  }    // warning不为空,命令界面输出warning  if (warnings.length) {    console.log('')    console.log(chalk.yellow('To use this template, you must update following to modules:'))    console.log()    for (var i = 0; i < warnings.length; i++) {      var warning = warnings[i]      console.log('  ' + warning)    }    console.log()    // 强制终结所有相关进程(也就是说如果版本号不符合,直接跳出进程)    process.exit(1)  }}

接下来继续看干了什么:

// 引入opn包(利用默认浏览器打开uri路径),express(web框架)var opn = require('opn')var express = require('express')// 搭建本地的服务器var app = express()// 引入config配置对象var config = require('../config')// 进程没有NODE_ENV的话,设置("production";"develpoment"或"testing")if (!process.env.NODE_ENV) {  process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)}// 端口号,没有默认值则使用配置值(8080)var port = process.env.PORT || config.dev.port// config配置对象里默认为truevar autoOpenBrowser = !!config.dev.autoOpenBrowser// app监听端口,成功后由回调(是否打开网页)module.exports = app.listen(port, function (err) {  if (err) {    console.log(err)    return  }  // when env is testing, don't need open it  if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {    opn(uri)  }})

好了,现在express()监听了端口,也可以自动打开了,但是里面没内容啊,而且热重载之类的是怎么做到的呢?

// 本地热重载的url路径var uri = 'http://localhost:' + port// 根据process.env.NODE_ENV选择加载不同的webpack配置var webpackConfig = process.env.NODE_ENV === 'testing'  ? require('./webpack.prod.conf')  : require('./webpack.dev.conf')var webpack = require('webpack')  var compiler = webpack(webpackConfig)// webpack-dev-middler是为webpack准备的中间件,服务webpack在连接服务器上导出的文件,开发专用// 只写在内存中,而不会占用硬盘空间// 如果文件发生改动,该中间件不再服务旧的打包文件,反而延迟请求直到编译结束。所以你没必要在因文件改动而刷新页面前等待。// Usage: app.use(webpackMiddleWare(webpack({}))var devMiddleware = require('webpack-dev-middleware')(compiler, {  publicPath: webpackConfig.output.publicPath,  quiet: true})app.use(devMiddleware)// 编译结束后回调devMiddleware.waitUntilValid(function () {  console.log('> Listening at ' + uri + '\n')})// 这个模块只和连接客户端与webpack服务器以及接受更新相关。它会接受服务器的更新然后执行这些更新。var hotMiddleware = require('webpack-hot-middleware')(compiler, {  log: () => {}})app.use(hotMiddleware)// 当html-webpack-plugin模板发生改变的时候,强制热加载compiler.plugin('compilation', function (compilation) {  compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {    hotMiddleware.publish({ action: 'reload' })    cb()  })})// 静态文件的输出路径,这里是"/static"var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)// 如果希望所有通过express.static访问的文件都存放在一个虚拟目录下面,可以通过为静态资源目录制定一个挂载路径的方式来实现,如下:app.use(staticPath, express.static('./static'))

还有一些。

// 单线程的代理中间件var proxyMiddleware = require('http-proxy-middleware')// 引入代理表,这里为空对象var proxyTable = config.dev.proxyTable/*** 关于这个模块的用法* var express = require('express');* var proxy = require('http-proxy-middleware');* * var app = express()** app.user('/api', proxy({target: 'http://www.example.org', changeOrigin: true}));* app.listen(3000);* http://localhost:3000/api/foo/bar -> http://www.example.org/api/foo/bar* 上面这个意思是,/api这个请求会被中间件导向目标host,支持正则表达*/Object.keys(proxyTable).forEach(function (context) {  var options = proxyTable[context]  if (typeof options === 'string') {    options = { target: options }  }  // 所有请求都将被导向新的host  app.use(proxyMiddleware(options.filter || context, options))})// 针对单页面应用的H5历史API回调// 单页面应用通常只使用一个index文件作为html文件,所以正常情况下后退浏览器会发生404。app.use(require('connect-history-api-fallback')())

3.看一看webpack.conf配置

先了解一下merge模块。

var merge = require('webpack-merge')module.exports = merge(baseWebpackConfig, {})

utils.js

再看一下工具类里面的函数。

// 这个模块的作用是把文本从bundle中提取出来放入一个单独的文件中// 用法// const ExtractTextPlugin = require("extract-text-webpack-plugin");// module.exports = {//  module: {//    rules: [//      {//        test: /\.css$/,//        use: ExtractTextPlugin.extract({//          fallback: "style-loader",//          use: "css-loader"//        })//      }//    ]//  },//    plugins: [//      new ExtractTextPlugin("styles.css"),//    ]// }// 上面这个的意思是把所有的*.css模块提取出来放到一个单独的文件中(styles.css),css打包和js打包是相互独立的var ExtractTextPlugin = require('extract-text-webpack-plugin')// 返回一个资源路径字符串,子目录下的路径exports.assetsPath = function (_path) {  var assetsSubDirectory = process.env.NODE_ENV === 'production'    ? config.build.assetsSubDirectory    : config.dev.assetsSubDirectory  return path.posix.join(assetsSubDirectory, _path)}/***** @************************ @return: {***     css: [                'vue-style-loader',                 [                    {                        loader: 'css-loader',                        options: {                            minimize: process.env.NODE_ENV === 'production',                            sourceMap: options.sourceMap                        }                    }                ]             ]*** }*** */exports.cssLoaders = function (options) {  options = options || {}  var cssLoader = {    loader: 'css-loader',    options: {      minimize: process.env.NODE_ENV === 'production',      sourceMap: options.sourceMap    }  }  // generate loader string to be used with extract text plugin  function generateLoaders (loader, loaderOptions) {    var loaders = [cssLoader]    if (loader) {      loaders.push({        loader: loader + '-loader',        options: Object.assign({}, loaderOptions, {          sourceMap: options.sourceMap        })      })    }    // Extract CSS when that option is specified    // (which is the case during production build)    if (options.extract) {      return ExtractTextPlugin.extract({        use: loaders,        fallback: 'vue-style-loader'      })    } else {      return ['vue-style-loader'].concat(loaders)    }  }  // http://vuejs.github.io/vue-loader/en/configurations/extract-css.html  return {    css: generateLoaders(),    postcss: generateLoaders(),    less: generateLoaders('less'),    sass: generateLoaders('sass', { indentedSyntax: true }),    scss: generateLoaders('sass'),    stylus: generateLoaders('stylus'),    styl: generateLoaders('stylus')  }}// Generate loaders for standalone style files (outside of .vue)exports.styleLoaders = function (options) {  var output = []  var loaders = exports.cssLoaders(options)  for (var extension in loaders) {    var loader = loaders[extension]    output.push({      test: new RegExp('\\.' + extension + '$'),      use: loader    })  }  return output}

webpack.base.conf

var path = require('path')// 工具类var utils = require('./utils')var config = require('../config')// vue-loader的options配置var vueLoaderConfig = require('./vue-loader.conf')// 返回dir的绝对路径位置function resolve (dir) {  return path.join(__dirname, '..', dir)}

上面定义了一些工具,具体配置如下:

const webpackConfig = {  // 入口文件,webpack从这个文件开始打包所有相关文件  entry: {    app: './src/apps/index.js'  },  // 输出路径  // 这里path: '../dist'  // publicPath: '/'  // '../dist/'  output: {    path: config.build.assetsRoot,    filename: '[name].js',    publicPath: process.env.NODE_ENV === 'production'      ? config.build.assetsPublicPath      : config.dev.assetsPublicPath  },  // extensions,扩展名是这些的文件,引用的时候可以省略  // alias,别名  resolve: {    extensions: ['.js', '.vue', '.json'],    alias: {      'vue$': 'vue/dist/vue.esm.js',      '@': resolve('src'),      'apps': path.resolve(__dirname, '../src/apps'),      'common': path.resolve(__dirname, '../src/common'),    }  },  // 模块规则,webpack可把其他资源(其他格式的文件)转换为js,利用的就是loader  module: {    rules: [      {        test: /\.vue$/,        loader: 'vue-loader',        options: vueLoaderConfig      },      {        test: /\.js$/,        loader: 'babel-loader',        include: [resolve('src'), resolve('test')]      },      {        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,        loader: 'url-loader',        query: {          limit: 10000,          name: utils.assetsPath('img/[name].[hash:7].[ext]')        }      },      {        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,        loader: 'url-loader',        query: {          limit: 10000,          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')        }      }    ]  }}

webpack.dev.conf

开发的配置主要是基础配置再合并了特殊的开发配置对象(主要是插件)。

// 这个webpack插件简单得创建了一些HTML文件来服务bundlesvar HtmlWebpackPlugin = require('html-webpack-plugin')var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')module.exports = merge(baseWebpackConfig, {    //exports.styleLoaders = function (options) {    //  var output = []    //  var loaders = exports.cssLoaders(options)    //  for (var extension in loaders) {    //    var loader = loaders[extension]    //    output.push({    //      test: new RegExp('\\.' + extension + '$'),    //      use: loader    //    })    //  }    //  return output    //}  module: {    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })  },  devtool: '#cheap-module-eval-source-map',  plugins: [    new webpack.DefinePlugin({      'process.env': config.dev.env    }),    new webpack.HotModuleReplacementPlugin(),    new webpack.NoEmitOnErrorsPlugin(),    new HtmlWebpackPlugin({      filename: 'index.html',      template: 'index.html',      inject: true,      chunks: ['app']    }),    new FriendlyErrorsPlugin()  ]})

webpack.prod.conf

var CopyWebpackPlugin = require('copy-webpack-plugin')var HtmlWebpackPlugin = require('html-webpack-plugin')var ExtractTextPlugin = require('extract-text-webpack-plugin')// 自动在webpack构建的过程中搜索css资源并压缩优化它们var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')

webpack.test.conf

3.npm run build

运行这行命令其实就是 node build/build.js

build.js

require('./check-versions')()process.env.NODE_ENV = 'production'// ora模块就是在终端显示优雅的旋转等待符号var ora = require('ora')var spinner = ora('building for production...')spinner.start()spinner.stop()
var rm = require('rimraf')rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {  if (err) throw err  webpack(webpackConfig, function (err, stats) {    spinner.stop()    if (err) throw err    process.stdout.write(stats.toString({      colors: true,      modules: false,      children: false,      chunks: false,      chunkModules: false    }) + '\n\n')    console.log(chalk.cyan('  Build complete.\n'))    console.log(chalk.yellow(      '  Tip: built files are meant to be served over an HTTP server.\n' +      '  Opening index.html over file:// won\'t work.\n'    ))  })})
原创粉丝点击