阅读 1356

如何学习配置webpack(一)

项目小白如何从0开始配置webpack

自己配置过webpack的人应该都知道,webpack真的好复杂,一开始做项目都是拿别人现成的做做小修改,但是别人的终究没有自己配的舒服。所以我打算写这篇文章,从我的配置中带大家了解webpack配置,简化读webpack官方页面的复杂度。

首先我们需要明确我们要求webpack做什么?

1.所写即所得,我们在编译器中写了代码能马上呈现在调试器上(热更新服务)

2.本地开发获取数据存在的跨域问题(代理,解决跨域)

3.使用es678,less,sass等(翻译,让浏览器懂得我们的代码)

4.提高项目性能,比如压缩代码,压缩图片等(项目优化)

5.如果与输入相关的需求,找entry(比如多页面就有多个入口) 6.如果与输出相关的需求,找output(比如你需要定义输出文件的路径、名字等等) 7.如果与模块寻址相关的需求,找resolve(比如定义别名alias) 8.如果与转译相关的需求,找loader(比如处理sass处理es678N) 9.如果与构建流程相关的需求,找plugin(比如我需要在打包完成后,将打包好的文件复制到某个目录,然后提交到git上) 接下来我们来看下webpack的一些基本配置

1.entry(项目入口)

2.output(出口文件)

3.modules(模块处理)

4.plugin(插件)

5.resolve

6.devserver

7.mode,

8devtool

1.项目入口设置(entry)

主要有三种方式

1.字符串形式

entry: '.src/main.js'
复制代码

2.数组形式

entry: [react, react-dom]
复制代码

3.对象形式

entry: {
    main:'./src/index2.js',
    second: './src/index2.js',
    vendor:['react', 'react-dom']
}
复制代码

2.出口文件设置及(output)

const path = require('path');
module.exports = {
  entry: './path/to/my/entry/file.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'my-first-webpack.bundle.js'
  }
};
复制代码

在上面的示例中,我们通过 output.filename 和 output.path 属性,来告诉 webpack bundle 的名称,以及我们想要 bundle 生成(emit)到哪里。 Path.resolve是什么,引入的path模块是干什么用的

Nodejs

该path.resolve()方法将一系列路径或路径段解析为绝对路径。

给定的路径序列从右到左处理,随后每个path路径都被预先加载,直到构造出绝对路径。例如,给定路径段的序列:/foo,/bar,baz,调用path.resolve('/foo', '/bar', 'baz')将返回/bar/baz。

如果在处理path完所有给定段之后尚未生成绝对路径,则使用当前工作目录。 生成的路径已规范化,并且除非将路径解析为根目录,否则将删除尾部斜杠。 零长度path段被忽略。

如果没有path传递段,path.resolve()将返回当前工作目录的绝对路径。

path.resolve('/foo/bar', './baz');
// Returns: '/foo/bar/baz'
path.resolve('/foo/bar', '/tmp/file/');
// Returns: '/tmp/file'
path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif');
// If the current working directory is /home/myself/node,
// this returns '/home/myself/node/wwwroot/static_files/gif/image.gif'
_dirname
复制代码

当前模块的目录名称。这是一样 path.dirname()的__filename。

示例:node example.js从中运行/Users/mjr

console.log(__dirname);
// Prints: /Users/mjr
console.log(path.dirname(__filename));
// Prints: /Users/mjr
复制代码

我们在配置output是常用的几个

filename 这个是输出文件的名称,字符串类型,如果只有一个输出文件,可以写成静态名称。例如

output:{
     filename:'bundle.js'
}
复制代码

当然了,在我们日常工作中,一般情况下是不会有这种情况的,当项目很大的时候,如果不分块打包,bundle.js会惊人的大,项目越大,bundle.js就会越大,这不是我们今天讨论的重点,以后再说

多个chunk的时候怎么办呢

webpack会为每个生成的Chunk取一个名称,Chunk的名称和Entry的配置有关:

1. 如果entry是一个string或者array,就只会生成一个chunk,这个chunk的名称是main;

2. 如果entry是一个object,就可能出现多个chunk,这时chunk的名称是object键值对里键的名称

然而,当通过多个入口起点(entry point)、代码拆分(code splitting)或各种插件(plugin)创建多个

bundle,应该使用以下一种替换方式,来赋予每个 bundle 一个唯一的名称…… 使用入口名称:

output:{
     filename: "[name].bundle.js"
}
复制代码

使用内部 chunk id

output:{
    filename: "[id].bundle.js"
}
复制代码

使用每次构建过程中,唯一的 hash 生成

output:{
filename: "[name].[hash].bundle.js"
}
复制代码

使用基于每个 chunk 内容的 hash:

output:{
filename: "[chunkhash].bundle.js"
}
复制代码

这里多出来几个陌生词汇hash、chunkhash,它们是什么? hash、chunkhash和contenthash三者的区别 hash

hash的值是相同的,如果都使用hash的话,因为这是工程级别的,即每次修改任何一个文件,所有文件名的hash至都将改变。所以一旦修改了任何一个文件,整个项目的文件缓存都将失效。所以对于没有改变的模块而言,这样做显然不恰当,因为缓存失效了嘛。此时,chunkhash的用途随之而来。 chunkhash

只有被修改了的文件的文件名,hash值修改

filename: '[name]-[chunkhash].js'

当我们使用mini-css-extract-plugin拆分css的时候,就需要使用chunkhash,我一个js文件里面引入了css文件。这时要是我修改了js,但没修改css,可以通过chunkhash缓存css文件

contenthash

对css使用了chunkhash之后,我们测试会发现,如果修改了js,css文件名的hash值确实没变,但这时要是我们修改css文件的话,我们就会发现css文件名的chunkhash值居然没变化,这样就导致我们的非覆盖发布css文件失效了。所以这里需要注意就是css文件必须使用contenthash。

上面介绍的 id、name、hash、chunkhash等都是webpack内置变量, id是唯一标示,不会重复,从0开始, name 是模块名称,是你自己起的,在配置路由懒加载的时候可以自己命名 官网介绍的很清楚,我就不再这里啰嗦了, chunkFilename

官网解释:此选项决定了非入口(non-entry) chunk 文件的名称, 什么场景需要呢?

在按需加载(异步)模块的时候,也就是路由懒加载,这样的文件是没有被列在entry中的,

比如

{
    entry: {
        "index": "pages/index.jsx"
    },
    output: {
         filename: "[name].min.js",
        chunkFilename: "[name].min.js"
    }
}
const myModel = r => require.ensure([], () => r(require('./myVue.vue')), 'myModel')
复制代码

上面的例子,通过filename输出的是index.min.js 异步加载的模块是要以文件形式加载哦,所以这时生成的文件名是以chunkname配置的,通过chunkFilename输出的是myModel.min.js 所以chunkFilename也很重要哦!!! path path是配置输出文件存放在本地的目录,字符串类型,是绝对路径

output:{
    path: path.resolve(__dirname, 'dist/assets')
}
复制代码

__dirname,这个昨天说过,可以回顾一下,就是当前文件所在的文件夹的名字 publicPath 对构建出的资源进行异步加载(图片,文件),该选项的值是以 runtime(运行时) 或 loader(载入时) 所创建的每个 URL 为前缀。因此,在多数情况下,此选项的值都会以/结束。 默认值是一个空字符串 "",即相对路径,配置错误会导致404 简单说,就是静态文件托管在cdn上 举个栗子: 如果你这么配置:

output:{
    filename:'[name]_[chunkhash:8].js',
    publicPath:'https://www.qdtalk.com/assets/'
}
复制代码

打包编译后,html页面就是这样的

path 和publicPath都支持字符串模板

  • 配置 单出口 // webpack 配置
const path = require('path');

module.exports = {
  entry: main: './src/main.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  }
};
复制代码
  • 配置 多出口
// webpack 配置
const path = require('path');

module.exports = {
  entry: {
    app: './src/app.js',
    vendors: './src/vendors.js'
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  }
};
复制代码

3.modules(模块处理)

模块处理主要是对loader的配置

此处引用官网对loader的定义

loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为webpack能够处理的有效模块,然后你就可以利用webpack的打包能力,对它们进行处理。

本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块。 注意,loader 能够 import 导入任何类型的模块(例如 .css 文件),这是 webpack 特有的功能,其他打包程序或任务执行器的可能并不支持。我们认为这种语言扩展是有很必要的,因为这可以使开发人员创建出更准确的依赖关系图。 在更高层面,在 webpack 的配置中 loader 有两个目标:

1. test 属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件。

2. use 属性,表示进行转换时,应该使用哪个 loader。

webpack.config.js
const path = require('path');
const config = {
  output: {
    filename: 'my-first-webpack.bundle.js'
  },
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' }
    ]
  }
};
module.exports = config;
复制代码

以上配置中,对一个单独的 module 对象定义了 rules 属性,里面包含两个必须属性:test 和 use。这告诉 webpack 编译器(compiler) 如下信息:

“嘿,webpack 编译器,当你碰到「在 require()/import 语句中被解析为 '.txt' 的路径」时,在你对它打包之前,先使用 raw-loader 转换一下。” 重要的是要记得,在 webpack 配置中定义 loader 时,要定义在 module.rules 中,而不是 rules。然而,在定义错误时 webpack 会给出严重的警告。为了使你受益于此,如果没有按照正确方式去做,webpack 会“给出严重的警告”

在webpack中有许许多多的loader,此处我按照官方文档解释下和我理解的用法 loader 特性

  • 几乎所有 loader 都 需要安装, 但 不需要 在 webpack 配置文件中通过 require 引入
  • 逆向编译,链式传递 文件
  • raw-loader 加载文件原始内容(utf-8)
  • val-loader 将代码作为模块执行,并将 exports 转为 JS 代码
  • url-loader 像 file loader 一样工作,但如果文件小于限制,可以返回 data URL
  • file-loader 将文件发送到输出文件夹,并返回(相对)URL raw-loader 可以让你在代码中引入文件
import txt from './file.txt';
复制代码

val-loader 加载的模块必须使用以下函数接口,将 default export 导出为一个函数。

function answer () {
  return {
    code: 'module.exports = 42;'
  }
};
module.exports = answer;
复制代码

url-loader

url-loader 功能类似于 file-loader,但是在文件大小(单位 byte)低于指定的限制时,可以返回一个 DataURL。 file-loader 将文件发送到输出文件夹,并返回(相对)URL(不会再对文件做处理) JSON

  • json-loader 加载 JSON 文件(默认包含)
  • json5-loader 加载和转译 JSON 5 文件
  • cson-loader 加载和转译 CSON 文件 主要处理json文件

转换编译(Transpiling)

  • script-loader 在全局上下文中执行一次 JavaScript 文件(如在 script 标签),不需要解析
  • babel-loader 加载 ES2015+ 代码,然后使用 Babel 转译为 ES5
  • buble-loader 使用 Bublé 加载 ES2015+ 代码,并且将代码转译为 ES5
  • traceur-loader 加载 ES2015+ 代码,然后使用 Traceur 转译为 ES5
  • ts-loader 或 awesome-typescript-loader 像 JavaScript 一样加载 TypeScript 2.0+
  • coffee-loader 像 JavaScript 一样加载 CoffeeScript

React开发过程中我们需要将jsx或者es6代码转译成es5代码。 我们需要用到babel-loader

{
'test': /\.(js|jsx)$/, // babel 转换为兼容性的 js
'exclude': /node_modules/,
'loader': 'babel-loader',
'query': {
'presets': ['react', 'latest', 'stage-0', 'react-hmre']
},
'include': path.resolve(__dirname, '../client')
},
复制代码

注:exclude是不转换node_modules的代码,query === options 此处注意babel版本6和7设置上有区别。 如果引用错误会报错 模板(Templating)

  • html-loader 导出 HTML 为字符串,需要引用静态资源
  • pug-loader 加载 Pug 模板并返回一个函数
  • jade-loader 加载 Jade 模板并返回一个函数
  • markdown-loader 将 Markdown 转译为 HTML
  • react-markdown-loader 使用 markdown-parse parser(解析器) 将 Markdown 编译为 React 组件
  • posthtml-loader 使用 PostHTML 加载并转换 HTML 文件
  • handlebars-loader 将 Handlebars 转移为 HTML
  • markup-inline-loader 将内联的 SVG/MathML 文件转换为 HTML。在应用于图标字体,或将 CSS 动画应用于 SVG 时非常有用。

样式

  • style-loader 将模块的导出作为样式添加到 DOM 中
  • css-loader 解析 CSS 文件后,使用 import 加载,并且返回 CSS 代码
  • less-loader 加载和转译 LESS 文件
  • sass-loader 加载和转译 SASS/SCSS 文件
  • postcss-loader 使用 PostCSS 加载和转译 CSS/SSS 文件
  • stylus-loader 加载和转译 Stylus 文件 如果我们在开发中使用了less或者sass,我们需要先转换成css在引入到项目中
{
'test': /\.less$/,
'loader': ['style-loader', 'css-loader', {
'loader': 'less-loader',
'options': {
'javascriptEnabled': true
}
}]
},
复制代码

此处的执行顺序。less-loader -> css-loader -> style-loader 清理和测试(Linting && Testing)

  • mocha-loader 使用 mocha 测试(浏览器/NodeJS)
  • eslint-loader PreLoader,使用 ESLint 清理代码
  • jshint-loader PreLoader,使用 JSHint 清理代码
  • jscs-loader PreLoader,使用 JSCS 检查代码样式
  • coverjs-loader PreLoader,使用 CoverJS 确定测试覆盖率

框架(Frameworks)

  • vue-loader 加载和转译 Vue 组件
  • polymer-loader 使用选择预处理器(preprocessor)处理,并且 require() 类似一等模块(first-class)的 Web 组件
  • angular2-template-loader 加载和转译 Angular 组件

核心重点(敲黑板)

plugins(插件)

作用:可以处理各种任务,从打包优化和压缩,一直到重新定义环境中的变量 loader不需要require. plugin需要

loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。

想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建它的一个实例。 下面是我配置的用于开发环境的plugins实例

webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装
const webpack = require('webpack'); // 用于访问内置插件
const config = {
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({template: './src/index.html'})
  ]
};
module.exports = config;
复制代码
'plugins': [
new webpack.optimize.OccurrenceOrderPlugin(), // 调整模块的打包顺序,用到次数更多的会出现在文件的前面
new webpack.DefinePlugin({ // DefinePlugin 允许创建一个在编译时可以配置的全局常量。
'process.env.NODE_ENV': JSON.stringify('development')
}),
new HtmlWebpackPlugin({ // HtmlWebpackPlugin简化了HTML文件的创建,以便为你的webpack包提供服务。这对于在文件名中包含每次会随着编译而发生变化哈希的 webpack bundle 尤其有用。 你可以让插件为你生成一个HTML文件,使用lodash模板提供你自己的模板,或使用你自己的loader。
'hash': true,
'title': 'Demo',
'filename': 'index.html',
'template': path.resolve(__dirname, '../views/index.ejs'),
'inject': 'body'
}),
new webpack.HotModuleReplacementPlugin(), // 启用热更新
new webpack.NoEmitOnErrorsPlugin(), // 输出阶段遇到编译错误跳过
new webpack.NamedModulesPlugin(), // 当开启 HMR 的时候使用该插件会显示模块的相对路径,建议用于开发环境。
new webpack.ProgressPlugin(), // 输出构建进度
]
复制代码
关注下面的标签,发现更多相似文章
评论