前端工程化

219 阅读6分钟

目标

  • webpack/babel工具的原理,配置详解及多场景实实践
  • 函数式编程

知识要点

webpack

构建步骤

  1. 分不同环境的config,包含了对应的一些配置

微信截图_20220115110948.png dev.env.js

'use strict'
const merge = require('webpack-merge')//使用webpack-merge工具去合并配置
const prodEnv = require('./prod.env')
​
module.exports = merge(prodEnv, {
  NODE_ENV: '"development"'
})
  1. 不同环境的webpack.config.js阿配置,以及prod环境构建文件build.js的配置

微信截图_20220115111403.png 其中utils.js 为构建loader的工具文件,webpack.base.conf.js为所有环境初始化的相同配置的文件,其它webpack的config文件就是对应相应的环境的不同配置

拿build.js和webpack.base.conf.js代码来观看

build.js

'use strict'
require('./check-versions')() // 检查node和npm版本
​
process.env.NODE_ENV = 'production' // 指定环境变量const ora = require('ora') // 一个美美的loading插件
const rm = require('rimraf') // rm -rf Node版本的unix命令
const path = require('path') // node自带的文件路径插件
const chalk = require('chalk') // 控制台高亮
const webpack = require('webpack')
const config = require('../config') // 配置入口
const webpackConfig = require('./webpack.prod.conf') // webpack配置const spinner = ora('building for production...')
spinner.start()
​
// config 三套环境 并行并且互相隔离的入口
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
  if (err) throw err
  webpack(webpackConfig, (err, stats) => {
    spinner.stop()
    if (err) throw err
​
    // webpack编译的开始
    process.stdout.write(stats.toString({
      colors: true,
      modules: false,
      children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
      chunks: false,
      chunkModules: false
    }) + '\n\n')
​
    if (stats.hasErrors()) {
      console.log(chalk.red('  Build failed with errors.\n'))
      process.exit(1)
    }
​
    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'
    ))
  })
})

webpack.dev.conf.js

'use strict'
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const path = require('path')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const portfinder = require('portfinder')
​
const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)
​
// dev.conf 的配置 和 base.conf的配置合并
const devWebpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
  },
  // cheap-module-eval-source-map is faster for development
  // 原理
  devtool: config.dev.devtool,
​
  // these devServer options should be customized in /config/index.js
  devServer: {
    clientLogLevel: 'warning',
    historyApiFallback: {
      rewrites: [
        { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
      ],
    },
    hot: true,
    contentBase: false, // since we use CopyWebpackPlugin.
    compress: true,
    host: HOST || config.dev.host,
    port: PORT || config.dev.port,
    open: config.dev.autoOpenBrowser,
    overlay: config.dev.errorOverlay
      ? { warnings: false, errors: true }
      : false,
    publicPath: config.dev.assetsPublicPath,
    proxy: config.dev.proxyTable, // 面试题:如何本地项目去做一些多端口服务的代理转发
    quiet: true, // necessary for FriendlyErrorsPlugin
    watchOptions: {
      poll: config.dev.poll,
    }
  },
  // 微内核
  plugins: [
    new webpack.DefinePlugin({
      'process.env': require('../config/dev.env')
    }),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
    new webpack.NoEmitOnErrorsPlugin(),
    // https://github.com/ampedandwired/html-webpack-plugin
    // 生成html模板文件
    // 面试题: 如何利用webpack去做依赖锁定?
    // 锁依赖固定板本 script
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.html',
      inject: true
    }),
    // copy custom static assets
    // 面试题:静态文件的移动&赋值
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.dev.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
  ]
})
​
module.exports = new Promise((resolve, reject) => {
  portfinder.basePort = process.env.PORT || config.dev.port
  portfinder.getPort((err, port) => {
    if (err) {
      reject(err)
    } else {
      // publish the new Port, necessary for e2e tests
      process.env.PORT = port
      // add port to devServer config
      devWebpackConfig.devServer.port = port
​
      // Add FriendlyErrorsPlugin
      devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
        compilationSuccessInfo: {
          messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
        },
        onErrors: config.dev.notifyOnErrors
        ? utils.createNotifierCallback()
        : undefined
      }))
​
      resolve(devWebpackConfig)
    }
  })
})

那么webpack的打包到底能做到什么事情呢?

webpack.*.config.js作用总结

从module.exports的每个属性讲解

  • context: 基础目录,绝对路径,用于从配置中解析入口点(entry point)和 加载器(loader)。

  • entry: 入口文件的配置,webpack会根据入口文件去找到所有import, requrie的依赖文件,并进行接下的识别,加载,编译,优化。

  • output: webpack把编译好的文件输出的地址,可以配置输出的文件名,资源前缀

  • devtool:配置是否生成sourceMap

  • devServer: 配置local启动时的host, port, 是否压缩,log的水平,是否启动默认打开浏览器,代理api的proxy等等

  • resolve: 用于配置需要扫描的文件类型,和一些路径或者文件的别名配置

  • module 主要的rules数组: 包含了对不同文件例如.vue,.js, .css, 图片对应loader的配置

  • resolveLoader: 主要配置自己写的loader的路径在哪里,这样可以在rule里面配置自己的loader

  • plugins: 配置插件在编译的每个阶段进行相应的控制与优化

    • DefinePlugin : 配置编译时的全局变量
    • HtmlWebpackPlugin: 生成html模板文件,把生成的scrprit 配置注入的index.html,可以利用webpack去做依赖锁定,配置特定script
    • CopyWebpackPlugin : 静态文件的移动&赋值
  • node: 这些选项可以配置是否 polyfill 或 mock 某些 Node.js 全局变量

    此功能由 webpack 内部的 NodeStuffPlugin 插件提供。

build.js文件 webpack打包启动文件

  • rm : 先删除原来的dist对应的文件夹目录
  • webpack:传入对应的webpack.config.js文件进行相应的打包,最后callback来判断打包是否成功,并控制台输出结果

优化场景

看我分享的脑图

函数式编程

特点

1. Vue3 React16.8 全面化函数式的推动
2. 函数式编程可以使得代码单元相对更加独立 - tree shaking过程更顺畅,更方便做UT
3. 减少了对this的依赖,减轻了开发人员对于指向问题的困惑
4. js天生友好函数式:ramda、loadsh

概念

1. 一种抽象运算过程
2. 函数式的函数并非对于过程运算,函数的映射
3. 幂等 - 相同的输入始终得到相同的输出
纯函数
let arr = [1, 2, 3, 4, 5];
​
arr.slice(0, 3); // [1, 2, 3]
arr.slice(0, 3); // [1, 2, 3]
​
arr.splice(0, 3); // [1, 2, 3]
arr.splice(0, 3); // [4, 5]

对于系统的改造

// 不纯的
let min = 18;
let limit = age => age > min;
​
// 纯纯的
let limit = age => age > 18;

对于大型系统来说,对于外部状态的依赖,会大大的提高系统复杂性

  • 问题: 18被硬编码到了函数内部的,造成了功能拓展的局限

高阶函数HOC

定义:

  1. 函数作为参数被传递到另一个函数中
  1. 函数作为返回值被另外一个函数返回
    let fn = arg => {
        let outer = "outer";
        let innerFn = () => {
            console.log(outer);
            console.log(arg);
        }
        return innerFn;
    }
​
    let closure = fn(18);
    // 闭包

函数柯里化

传递给函数一部分参数用于功能调用,让他返回一个函数去处理剩下的参数

    let add = (x, y) => x + y;
​
    // 柯里化后
    let add = x => (y => x + y);
​
    let add2 = add(2);
    let add200 = add(200);
​
    add2(2); // 2 + 2 add(2)(2)
    add200(50); // 200 + 50
​
    // 回到上面的limit, 纯函数化
    let limit = min => (age => age > min);
    let limit18 = limit(18);
    limit18(20); // true

是一种预加载方式

  • 问题 包心菜代码的产生h(g(f(x)));
#### 组合
> 通过更优雅的方式实现纯函数的解耦
​
```js
let compose = (f, g) => (x => f(g(x)));
​
let add1 = x => x + 1;
let mul5 = x => x * 5;
​
compose(mul5, add1)(2); // 15
​
// 面试题 - 数组长度未知的情况下,拿到最后一项
let first = arr => arr[0];
let reverse = arr => arr.reverse();
​
let last = compose(first, reverse);
​
last([1, 2, 3, 4, 5]); // 5
```

补充知识点

webpack

思考一个问题,webpack的配置如此繁多,如何才可以学好它呢。我觉得分为以下几点

  • 需要熟知webpack的配置属性有那些,能做到什么样的事情,不需要去每一个loader, 插件都需要了解
  • 了解如何去做各种优化,它涉及的点在哪里
  • 知道知识来源路径在哪,在哪可以找到对应的文档去,了解,解决问题

所以我总结了一个语雀脑图,包括wepack的配置,优化,demo, loader, 插件,好的学习文章,包含了知识url索引

www.yuque.com/docs/share/…(密码:ia93) 《webpack》

后续会不断更新知识要点