webpack通俗易懂(四)

webpack通俗易懂(四)

  • 1,source-map
  • 2,devServer
  • 3, https
  • 4,eslint
  • 5,git-hooks 与 husky

辛苦编写良久,还望点赞鼓励呦~

1,提高开发效率,完善团队开发规范

1.1,source-map

之前我们通过webpack, 将我们的源码打包成了 bundle.js, 实际上客户端(浏览器)读取的是打包后的 bundle.js, 当浏览器执行代码报错的时候,报错的信息自然也是 bundle 的内容。我们如何将报错信息(bundle错误的语句及其所在行列)映射到源码上,这时候我们就需要用 source-map了,webpack已经内置了sourcemap功能,我们只需要通过简单的配置,就可以开启它

module.exports = {
  // 开启 source map
  // 开发中推荐使用 'source-map' // 生产环境一般不开启 sourcemap 
  devtool: 'source-map'
}

当我们执行打包命令后,我们发现bundle的最后一行总是会多出一个注释,指向打包出的bundle.map.js(sourcemap文件)。 sourcemap文件用来描述源码文件和 bundle 文件的代码位置映射关系。基于它,我们将bundle文件的错误信息映射到源码文件上。
除开'source-map'外,还可以基于我们的需求设置其他值,webpack——devtool一共提供了7种SourceMap模式:

  • eval:每个module会封装到 eval 里包裹起来执行,并且会在末尾追加注释 //@ sourceURL.
  • source-map:生成一个SourceMap文件
  • hidden-source-map:和 source-map一样,但不会在 bundle 末尾追加注释
  • inline-source-map:生成一个 DataUrl 形式的 SourceMap 文件.
  • eval-source-map:每个module会通过eval()来执行,并且生成一个DataUrl形式的 SourceMap
  • cheap-source-map:生成一个没有列信息(column-mappings)的SourceMaps文 件,不包含loader的 sourcemap(譬如 babel 的 sourcemap)
  • cheap-module-source-map:生成一个没有列信息(column-mappings)的SourceMaps文件,同时 loader 的 sourcemap 也被简化为只包含对应行的。

要注意的是,生产环境我们一般不会开启sourcemap功能,主要有两点原因:

  1. 通过bundle和sourcemap文件,可以反编译出源码————也就是说,线上产物有soucemap文件的话,就意味着有暴漏源码的风险。
  2. 我们可以观察到,sourcemap文件的体积相对比较巨大,这跟我们生产环境的追求不同(生产环境追求更小更轻量的bundle)。

有时候我们期望能第一时间通过线上的错误信息,来追踪到源码位置,从而快速解决掉bug以减轻损失。但又不希望sourcemap文件报漏在生产环境,有什么比较好的方案呢?

1.2,devServer

开发环境下,我们往往需要启动一个web服务,方便我们模拟一个用户从浏览器中访问我们的web服务,读取我们的打包产物,以观测我们的代码在客户端的表现。webpack内置了这样的功能,我们只需要简单的配置就可以开启它。

安装 devServer

yarn add -D webpack-dev-server

devServer.proxy基于强大的中间件 http-proxy-middleware 实现的,因此它支持很多的配置项,我们基于此,可以做应对绝大多数开发场景的定制化配置。
基础使用:

const path = require('path')
devServer: {
  static: {
    directory: path.join(__dirname, 'dist')
  }, // 默认是把dist目录作为web服务的根目录
  compress: true, // 可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
  port: 3000, // 端口号
},

为了方便,我们配置一下工程的脚本命令,在package.json的scripts里:

{
  "scripts": {
    "dev": "webpack serve --mode development"
  }
}
  • 1.2.1, 添加响应头

有些场景需求下,我们需要为所有响应添加headers, 来对资源的请求和响应打入标志,以便做一些安全防范,或者方便发生异常后做请求的链路追踪。比如:

module.exports = {
  devServer: {
    headers: {
      'X-Token': 'ZlcjLCe+sAW1S4QC8Z'
    }
  }
}
image
  • 1.2.2, 开启代理

我们打包出的js bundle里有时会含有一些对特定接口的网络请求(ajax/fetch). 要注意,此时客户端地址是在 http://localhost:3000/ 下,假设我们的接口来自 http://localhost:4001/,那制台就会报错跨域,在开发环境下,我们可以使用devServer自带的proxy功能来解决这个问题。

我们新搭建一个服务,在当前项目下新建 server.js:

const http = require('http');
const app = http.createServer((req, res) => {
  if (req.url === '/api/user') {
    res.end('hello node')
  }
})

app.listen(4001, 'localhost', () => {
  console.log('localhost listening on 4001')
})

再次打开一个终端执行node server.js,启动服务

image

浏览器输入:

image

下面我们开始请求,请求我们可以使用浏览器自带的方法fetch,这个方法返回的是一个promise

fetch('/api/user')
  .then(val => val.text()) // res.text()可以把返回的结果变成文本)
  .then(res => {
    console.log(res)
  })
image

如何解决上面跨域的问题呢:

module.exports = {
  //...
  devServer: {
    proxy: {
      '/api': 'http://localhost:4001'
    }
  } 
}
// 如果用户在地址栏一旦请求了一个资源叫 /api 的话,我们就给他指向到 http://localhost:4001 服务器上去

现在对 /api/user 的请求会将请求代理到 http://localhost:4001/api/user。如果不希望传递 /api, 则可以重写路径:
proxy: {
  '/api': {
    target: 'http://localhost:4001',
    pathRewrite: {
      '^/api': '/'  // 这里可以是'/'也可以是''
    }
  }
}
image
  • 1.2.3,https

默认情况下,将不接受在 HTTPS 上运行且证书无效的后端服务器。如果想让我们的本地http服务改为https服务,可以这样配置:

devServer: {
  https: true
}

重新启动服务:npx webpack, 我们发现访问http://localhost:port是无法访问我们的服务的,我们需要在地址栏里加前缀: https,注意: 由于默认配置使用的是自签名证书,所以有的浏览器会告诉你是不安全的,但我们依然可以继续访问它。当然我们也可以提供自己的证书:

module.exports = {
  devServer: {
    https: {
      cacert: './server.pem',
      pfx: './server.pfx',
      key: './server.key',
      cert: './server.crt',
      passphrase: 'webpack-dev-server',
      requestCert: true,
    }
  }
}
image
  • 1.2.4, http2

我们也可以不使用https,可以使用http2

如果想要配置http2,那么直接设置:

devServer: {
   http2: true
}

http2默认自带https自签名证书,当然我们仍然可以通过https配置项来使用自己的证书

image
  • 1.2.5, historyApiFallback

如果我们的应用是个SPA(单页面应用),当路由到/some 时(可以直接在地址栏里输入),会发现此时刷新页面后,控制台会报错:

GET http://localhost:3000/some 404 (Not Found)

此时打开network,刷新并查看,就会发现问题所在———浏览器把这个路由当作了静态资源的地址去请求,然而我们并没有打包出/some这样的资源,所以这个访问无疑是404的。如何解决它?我们可以通过配置来提供页面代替任何404的静态资源响应:

module.exports = {
  //...
  devServer: {
    historyApiFallback: true
  }
}

此时重启服务刷新后发现请求变成了index.html, 当然, 在多数业务场景下,我们需要根据不同的访问路径定制替代的页面,这种情况下,我们可以使用rewrites这个配置项。 类似这样:

module.exports = {
  //...
  devServer: {
    historyApiFallback: {
      rewrites: [
        { from: /^\/$/, to: '/views/landing.html' },
        { from: /^\/subpage/, to: '/views/subpage.html' },
        { from: /./, to: '/views/404.html' },
      ]
    }
  }
}
  • 1.2.6, 开发服务器主机

如果我们在开发环境中起了一个devserve服务,并希望在同一局域网下的同事也能访问它,只需要配置:

devServer: {
  host: '0.0.0.0'
}

这时候,如果我们的同事跟我们处在同一局域网下,就可以通过局域网ip来访问我们的服务 啦

image

1.3, 模块热替换与热加载

  • 模块热替换
    模块热替换(HMR - hot module replacement)功能会在应用程序运行过程中,
    替换、添加或删除 模块,而无需重新加载整个页面
module.exports = {
  //...
  devServer: {
    hot: true,
  },
}

HMR 加载样式,如果我们配置了style-loader,那么现在已经同样支持样式文件的
热替换功能了

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
}

这是因为style-loader的实现使用了module.hot.accept,在CSS依赖模块更新之后,
会对 style 标签打补丁。从而实现了这个功能。
热加载(文件更新时,自动刷新我们的服务和页面) 新版的webpack-dev-server
默认已经开启了热加载的功能。 它对应的参数是devServer.liveReload,默认为
true。 注意,如果想要关掉它,要将liveReload设置为false的同时,也要关掉
hot

image
module.exports = {
  //...
  devServer: {
    liveReload: false, //默认为true,即开启热更新功能。
  },
};

1.4,eslint

eslint是用来扫描我们所写的代码是否符合规范的工具。往往我们的项目是多人协作开发的,我们期望统一的代码规范,这时候可以让eslint来对我们进行约束。严格意义上来说,eslint配置跟webpack无关,但在工程化开发环境中,它往往是不可或缺的

yarn add eslint -D
npx eslint --init

我们可以看到控制台里的展示:

image

并生成了一个配置文件(.eslintrc.json),这样我们就完成了eslint的基本规则配置。 eslint配置文件里的配置项含义如下:

    1. env 指定脚本的运行环境。每种环境都有一组特定的预定义全局变量。此处使用的 browser 预定义了浏览器环境中的全局变量,es6 启用除了 modules 以外的所有 ECMAScript 6 特性(该选项会自动设置 ecmaVersion 解析器选项为 6)。
    1. globals 脚本在执行期间访问的额外的全局变量。也就是 env 中未预定义,但我
      们又需要使用的全局变量。
    1. extends 检测中使用的预定义的规则集合。
    1. rules 启用的规则及其各自的错误级别,会合并 extends 中的同名规则,定义冲
      突时优先级更高。
    1. parserOptions ESLint 允许你指定你想要支持的 JavaScript 语言选项。
      ecmaFeatures 是个对象,表示你想使用的额外的语言特性,这里 jsx 代表启用 JSX。ecmaVersion 用来指定支持的 ECMAScript 版本 。默认为 5,即仅支持 es5,你可以使用 6、7、8、9 或 10 来指定你想要使用的 ECMAScript 版本。你 也可以用使用年份命名的版本号指定为 2015(同 6),2016(同 7),或 2017(同 8)或 2018(同 9)或 2019 (same as 10)。上面的 env 中启用了 es6,自动设置了ecmaVersion 解析器选项为 6。 plugins plugins 是一个 npm 包,通常输出 eslint 内部未定义的规则实现。rules 和 extends 中定义的规则, 并不都在 eslint 内部中有实现。比如 extends 中的 plugin:react/recommended,其中定义了规则开关和等级,但是这些规则如何 生效的逻辑是在其对应的插件 ‘react’ 中实现的

新建项目文件夹,并在继承终端中打开:

yarn init -y
npm install eslint -D
npx eslint --init

新建src -> app.js

// app.js
console.log('hello eslit')

npx eslint ./src
// eslintrc.json
{
  "rules": {
    "no-console": "warn" // 可以在rules中自定义约束规范
  }
}

执行npx eslint ./src就可以检测出代码是否存在语法错误等规范问题,我们可以在控制台看出哪些不符合规范,这样并不直观,我们可以通过安装vscode插件eslint醒目的看到代码上有红色波浪线〰️。

我们可以通过命令来让elisnt检测代码——在我们的package.scripts里添加一个脚本命令:

// package.json
{
  "scripts": {
    "eslint": "eslint ./src"
  }
}

然后执行

eslint src

以上我们直观的看到代码规范错误是通过安装vscode插件,如果不想使用插件,又想实时提示报错,我们可以结合 webpack 的打包编译功能来实现。

rules: [
  {
    test: /\.(js|jsx)$/,
    exclude: /node-modules/,
    use: ['babel-loader', 'eslint-loader']
  }
]

因为我们使用了devServer,因此需要在devServer下添加一个对应的配置参数:

module.exports = {
  devServer: {
    liveReload: false, //默认为true,即开启热更新功能。
  }
}

现在我们就可以实时地看到代码里的不规范报错啦

1.5,git-hooks 与 husky

为了保证团队里的开发人员提交的代码符合规范,我们可以在开发者上传代码时进行校验。我们常用 husky 来协助进行代码提交时的 eslint 校验。在使用husky之前,我们先来研究一下 git-hooks

我们回到项目的根目录下。git init, ls -a 命令 ———— “-a”可以显示隐藏目录(目录名的第一位是.)

image

接来下我们进入到这个文件夹,进一步查看它内部的内容

cd .git
ls -a
image

可以看到,当前目录下存在一个hooks文件夹,顾名思义,这个文件夹提供了git 命令相关的钩子

cd hooks
ls -a
image

那我们可以看到有很多git命令相关的文件名。比如"pre-commit.sample pre- push.sample"。 回到正题——我们期望在git提交(commit)前,对我们的代码进行检测,如果不能通 过检测,就无法提交我们的代码, 这个动作的时机应该是?————"pre commit", 也就是 commit之前。

现在,我们查看一下pre-commit.sample的内容

# cat命令可以查看一个文件的内容 
cat pre-commit.sample

OK,它返回了这样的内容,是一串shell注释。翻译过来大概意思是,这是个示例钩子,然后我们看到了这一句话

# To enable this hook, rename this file to "pre-commit"

意思是要启用这个钩子的话,我们就把这个文件的后缀名去掉。

虽然这样对我们本地来讲是可行的,但要注意,.git文件夹的改动无法同步到远端仓库

所以我们期望将git-hook的执行权移交到外面来

好的,我们回到项目的根目录下,然后我们新建一个文件夹,暂时命名为".mygithooks" 然后在此文件夹下,新增一个git-hook文件,命名为"pre-commit",并写入以下内容:

echo pre-commit执行啦

好了,我们新建了自己的git-hook,但此时git并不能识别。下面我们执行这行命令:

# 项目根目录下
git config core.hooksPath .mygithooks

上述命令给我们自己的文件,配置了git-hook的执行权限。
但这个时候我们git commit的话,可能会报这样的waring,并且没有执行我们的
shell:

hint: The 'pre-commit' hook was ignored because it's not set as
executable.
hint: You can disable this warning with `git config
advice.ignoredHook false`

这是因为我们的操作系统没有给出这个文件的可执行权限。
因此我们得再执行这样一句命令:

chmod +x .mygithooks/pre-commit

ok!现在我们尝试执行git add . && git commit -m "any meesage"。 我们发现控制台日志会先打印 “pre-commit执行啦”。 这意味着成功啦!

总结:

也就是说,我们搞git-hook的话,要分三步走:

    1. 新增任意名称文件夹以及文件pre-commit(这个文件名字比如跟要使用的git- hook名字一致)!
    1. 执行以下命令来移交git-hook的配置权限
git config core.hooksPath .mygithooks
    1. 给这个文件添加可执行权限:
chmod +x .mygithooks/pre-commit

然后就成功啦。 这时候我们可以在pre-commit里写任意脚本,比如:

eslint src

当eslint扫描代码,出现error时,会在结束扫描时将退出码设为大于0的数字。 也就是会报错,这时候commit就无法往下执行啦,我们成功的拦截了此次错误操作。

husky

husky在升级到7.x后,做了跟我们上述同样的事。 安装它之前,我们需要在package.json中的script里,先添加

"sctript": {
    //...others
    "prepare": "husky install"
}

prepare是一个npm钩子,意思是安装依赖的时候,会先执行husky install命令。 这个命令就做了上述的123这三件事! 我们安装了7.x的husky会发现,项目根目录下生成了.husky的文件夹。 当然,7.x的husky似乎是有bug的,如果不能正常使用,那么我们只需要验证两件事:

    1. 是否移交了git-hook的配置权限?
      执行命令 "git config --list"查看core.hooksPath配置是否存在,是否正确指向 了.husky。
      如果没有,我们只需要手动的给加上就行:
git config core.hooksPath .husky
  1. 是否是可执行文件? 参考上述总结中的3即可 这时我们的husky就正常了

你可能感兴趣的:(webpack通俗易懂(四))