webpack从零的实践(新手良药)
1. 什么是webpack?
本质上,webpack是一个现代javascript应用程序的静态模块打包器。webpack处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
2.模块
在模块化编程中,开发者将程序分解成离散的功能块,将之称为模块。
每个模块具有比完整程序更小的接触面,使得校验、调试、测试轻而易举。 精心编写的_模块_提供了可靠的抽象和封装界限,使得应用程序中每个模块都具有条理清楚的设计和明确的目的。 (设计模式单一原则)
3.Hello Webpack webpack从4.0后,可以在不引用配置文件的情况下使用。下面通过一个例子简单的看下webpack的使用:
mkdir webpack-demo && cd webpack-demo
npm init -y
npm install webpack webpack-cli --save-dev
4.0后需要安装webpack-cli来使用webpack的一些功能(CLI即Command Line Interface,顾名思义,也就是命令行用户界面。)
现在我们创建如下的目录结构和内容:
webpack-demo
|- package.json
|- index.html
|- /src
   |- index.js
index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Webpack-Demo</title>
    <script src="https://unpkg.com/lodash@4.16.6"></script>
</head>
<body>
    <srcipt src="./src/index.js"></srcipt>
</body>
</html>
index.js
function component() {
  var element = document.createElement('div');
  // Lodash(目前通过一个 script 脚本引入)对于执行这一行是必需的
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  return element;
}
document.body.appendChild(component());
接下来对package.json做一些调整,去掉main,增加private的属性为true,已确保我们的项目是私有的,避免意外发布:
{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
 +"private": true,
 -"main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.15.1",
    "webpack-cli": "^3.0.8"
  }
}
这时候,我们在控制台执行
npx webpack
运行成功后,我们可以看到目录下新增来一个dist文件夹
webpack-demo
   +|- /dist
   +   |- index.js
    |- package.json
    |- index.html
    |- /src
       |- index.js
webpack默认配置会生成打包文件dist,项目打包为main.js
在此示例中,<script> 标签之间存在隐式依赖关系。index.js 文件执行之前,还依赖于页面中引入的 lodash。之所以说是隐式的是因为 index.js 并未显式声明需要引入 lodash,只是假定推测已经存在一个全局变量 _。
使用这种方式去管理 JavaScript 项目会有一些问题:
- 无法立即体现,脚本的执行依赖于外部扩展库(external library)。
 - 如果依赖不存在,或者引入顺序错误,应用程序将无法正常运行。
 - 如果依赖被引入但是并没有使用,浏览器将被迫下载无用代码。
 
接下来我们针对上面提出的一些问题对代码进行一些调整 首先我们通过npm安装lodash库到我们的项目中:
npm install --save lodash
修改index.js
+ import _ from 'lodash';
function component() {
    var element = document.createElement('div');
    element.innerHTML = _.json(['Hello','Webpack'],' ');
    return element;
}
document.body.appendChild(component());
在dist文件夹中新作增dist/index.html
<!doctype html>
  <html>
   <head>
     <title>Webpack-Demo</title>
   </head>
   <body>
        <script src="main.js"></script>
   </body>
  </html>
在控制台重新运行
npx webpack
这时候在浏览器中打开 /dist/index.html,如果一切访问都正常,你应该能看到以下文本:'Hello webpack'。
4.基础的四个核心概念
虽然我们可是在不进行任何配置的情况下可以使用webpack,但是在复杂的项目中,我们仍然需要使用配置文件对webpack做相应的设置已满足我们的需求。接下来我们将一步步的了解webpack的四个核心概念。
- 入口(entry)
 - 输出(output)
 - loader
 - 插件(plugins)
 
首先我们在根目录下创建webpack.config.js文件
const path = require('path');
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};
现在我们通过新的配置文件再次执行构建
npx webpack --config webpack.config.js
执行结束后,我们可以在dist目录下发现新生成来bundle.js文件,我们修改dist目录下的index.html文件:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    - <script src="main.js"></script>
    + <script src="bundle.js"></script>
</body>
</html>
修改后我们再在浏览器中运行,可以得到同样的结果。
我们回来看下webpack.config.js 我们定义来一个entry和output
4.1 入口 这里的entry就是入口起点。 入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。
每个依赖项随即被处理,最后输出到称之为 bundles 的文件中。
4.2 出口 output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist。基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。
在上面的示例中,我们通过 output.filename 和 output.path 属性,来告诉 webpack bundle 的名称,以及我们想要 bundle 生成(emit)到哪里。可能你想要了解在代码最上面导入的 path 模块是什么,它是一个 Node.js 核心模块,用于操作文件路径。
如果每次都通过cli的方式来打包或者做其他的操作,我们会发现很麻烦,特别是在复杂情况下需要通过cli运行很多配置参数的时候。
这时候,我们可以借助NPM Scripts来做一些简化工作。我们修改package.json
{
    "name": "webpack-demo",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",
+     "build": "webpack"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "devDependencies": {
      "webpack": "^4.0.1",
      "webpack-cli": "^2.0.9",
      "lodash": "^4.17.5"
    }
  }
这时候我们就可以通过npm run build来替代npx命令运行
5.资源管理
webpack 最出色的功能之一就是,除了 JavaScript,还可以通过 loader 引入任何其他类型的文件。也就是说,以上列出的那些 JavaScript 的优点(例如显式依赖),同样可以用来构建网站或 web 应用程序中的所有非 JavaScript 内容。
接下来我们尝试整合一些其他资源,比如图像,看看 webpack 如何处理。
5.1加载CSS
为了可以在JavaScript模块中通过import引入一个css文件,需要先添加style-loader和css-loader。
npm install --save-dev style-loader css-loader
webpack.config.js
const path = require('path');
  module.exports = {
    entry: './src/index.js',
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist')
    },
+   module: {
+     rules: [
+       {
+         test: /\.css$/,
+         use: [
+           'style-loader',
+           'css-loader'
+         ]
+       }
+     ]
+   }
  };
我们尝试一次,通过在项目中添加一个新的style.css文件,并将其导入到index.js中。
src/style.css
.hello {
    color: red;
}
src/index.js
import _ from 'lodash';
+ import './style.css';
  function component() {
    var element = document.createElement('div');
    element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+   element.classList.add('hello');
    return element;
  }
  document.body.appendChild(component());
运行构建命令
npm run build
构建成功后在浏览器中访问dist/index.html可以看到红色的Hello webpack
5.2 loader
loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。
本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块。
通过上面的事例,我们可以看到loader中的的两个参数配置:
- test 属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件。
 - use 属性,表示进行转换时,应该使用哪个 loader。
 
webpack 编译器,当你碰到「在 require()/import 语句中被解析为 '.css' 的路径」时,在你对它打包之前,先使用 style-loader和css-loader 转换一下。
loader 用于对模块的源代码进行转换。loader 可以使你在 import 或"加载"模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import CSS文件!
5.3 使用loader
有三种使用 loader 的方式:
- 配置(推荐):在 webpack.config.js 文件中指定 loader。
 - 内联:在每个 import 语句中显式指定 loader。
 - CLI:在 shell 命令中指定它们。
 
配置的方式我们已经在上面讲了,如果对其他两种方式有兴趣,可以自行参考官方文档进行试验。
5.3 加载图片 接下来我们再看怎么加载图片资源,我们先安装file-loader
npm install --save-dev file-loader
webpack.config.js
const path = require('path');
  module.exports = {
    entry: './src/index.js',
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist')
    },
    module: {
      rules: [
        {
          test: /\.css$/,
          use: [
            'style-loader',
            'css-loader'
          ]
        },
+       {
+         test: /\.(png|svg|jpg|gif)$/,
+         use: [
+           'file-loader'
+         ]
+       }
      ]
    }
  };
我们向src中加入一张图片,然后在./src/index.js中引用
import _ from 'lodash';
  import './style.css';
+ import Icon from './icon.png';
  function component() {
    var element = document.createElement('div');
    // Lodash,现在由此脚本导入
    element.innerHTML = _.join(['Hello', 'webpack'], ' ');
    element.classList.add('hello');
+   // 将图像添加到我们现有的 div。
+   var myIcon = new Image();
+   myIcon.src = Icon;
+
+   element.appendChild(myIcon);
    return element;
  }
  document.body.appendChild(component());
src/style.css
.hello {
    color: red;
+   background: url('./icon.png');
  }
让我们重新构建,并再次打开 index.html 文件,和 Hello webpack 文本旁边的 img 元素一样,现在看到的图标是重复的背景图片。如果你检查此元素,你将看到实际的文件名已更改为像 5c999da72346a995e7e2718865d019c8.png 一样。这意味着 webpack 在 src 文件夹中找到我们的文件,并成功处理过它!
合乎逻辑下一步是,压缩和优化你的图像。查看 image-webpack-loader 和 url-loader,以了解更多关于如果增强加载处理图片功能。
加载更多其他类型的文件可以参考官方文档。
6. 输入管理
现在我们在src中新增一个js文件,并在src/index.js中使用
src/print.js
export default function printMe() {
  console.log('I get called from print.js!');
}
src/index.js
import _ from 'lodash';
+ import printMe from './print.js';
  function component() {
    var element = document.createElement('div');
+   var btn = document.createElement('button');
    // Lodash(目前通过一个 script 脚本引入)对于执行这一行是必需的
    element.innerHTML = _.join(['Hello', 'webpack'], ' ');
    element.classList.add('hello');
    var myIcon = new Image();
    myIcon.src = icon;
    element.appendChild(myIcon);
+   btn.innerHTML = 'Click me and check the console!';
+   btn.onclick = printMe;
+
+   element.appendChild(btn);
    return element;
  }
  document.body.appendChild(component());
修改webpack.config.js,将将在 entry 添加 src/print.js 作为新的入口起点(print),然后修改 output,以便根据入口起点名称动态生成 bundle 名称:
const path = require('path');
  module.exports = {
-   entry: './src/index.js',
+   entry: {
+     app: './src/index.js',
+     print: './src/print.js'
+   },
    output: {
-     filename: 'bundle.js',
+     filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };
执行构造,然后我们可以发现在dist目录下增加来app.bundle.js和print.bundle.js此时我们想正确的运行程序,就需要修改dist/index.html
<!doctype html>
  <html>
    <head>
-     <title>Asset Management</title>
+     <title>Output Management</title>
+     <script src="./print.bundle.js"></script>
    </head>
    <body>
-     <script src="./bundle.js"></script>
+     <script src="./app.bundle.js"></script>
    </body>
  </html>
到目前为止,我们都是通过手动引入所有资源到index.html 文件中,然而随着应用程序增长,并且一旦开始对文件名使用哈希(hash)]并输出多个 bundle,手动地对 index.html 文件进行管理,一切就会变得困难起来。因此我们用 HtmlWebpackPlugin 来解决这个问题。
6.1 设定HtmlWebpackPlugin
首先安装插件,并调整 webpack.config.js
npm install --save-dev html-webpack-plugin
webpack.config.js
const path = require('path');
+ const HtmlWebpackPlugin = require('html-webpack-plugin');
  module.exports = {
    entry: {
      app: './src/index.js',
      print: './src/print.js'
    },
+   plugins: [
+     new HtmlWebpackPlugin({
+       title: 'Output Management'
+     })
+   ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };
在我们构建之前,你应该了解,虽然在 dist/ 文件夹我们已经有 index.html 这个文件,然而 HtmlWebpackPlugin 还是会默认生成 index.html 文件。这就是说,它会用新生成的 index.html 文件,把我们的原来的替换。让我们看下在执行 npm run build 后会发生什么: 如果你在代码编辑器中将 index.html 打开,你就会看到 HtmlWebpackPlugin 创建了一个全新的文件,所有的 bundle 会自动添加到 html 中。
如果你想要了解更多 HtmlWebpackPlugin 插件提供的全部功能和选项,那么你就应该多多熟悉 HtmlWebpackPlugin 仓库。
你还可以看一下 html-webpack-template,除了默认模板之外,还提供了一些额外的功能。
6.2 清理/dist文件夹
你可能已经注意到,由于过去的指南和代码示例遗留下来,导致我们的 /dist 文件夹相当杂乱。webpack 会生成文件,然后将这些文件放置在 /dist 文件夹中,但是 webpack 无法追踪到哪些文件是实际在项目中用到的。
通常,在每次构建前清理 /dist 文件夹,是比较推荐的做法,因此只会生成用到的文件。让我们完成这个需求。
clean-webpack-plugin 是一个比较普及的管理插件,让我们安装和配置下。
npm install clean-webpack-plugin --save-dev
webpack.config.js
const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');
+ const CleanWebpackPlugin = require('clean-webpack-plugin');
  module.exports = {
    entry: {
      app: './src/index.js',
      print: './src/print.js'
    },
    plugins: [
+     new CleanWebpackPlugin(['dist']),
      new HtmlWebpackPlugin({
        title: 'Output Management'
      })
    ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };
现在执行 npm run build,再检查 /dist 文件夹。如果一切顺利,你现在应该不会再看到旧的文件,只有构建后生成的文件!
你可能会感兴趣,webpack及其插件似乎“知道”应该哪些文件生成。答案是,通过 manifest,webpack 能够对「你的模块映射到输出 bundle 的过程」保持追踪。如果你对通过其他方式来管理 webpack 的输出更感兴趣,那么首先了解 manifest 是个好的开始。
通过使用 WebpackManifestPlugin,可以直接将数据提取到一个 json 文件,以供使用。
我们不会在此展示一个关于如何在你的项目中使用此插件的完整示例,但是你可以仔细深入阅读 manifest 的概念页面,以及通过缓存指南来弄清如何与长期缓存相关联。
6.3 插件【plugins】
插件是 webpack 的支柱功能。webpack 自身也是构建于你在 webpack 配置中用到的相同的插件系统之上!
插件目的在于解决 loader 无法实现的其他事。
由于插件可以携带参数/选项,你必须在 webpack 配置中,向 plugins 属性传入 new 实例。
7 开发 到目前为止,我们每次修改代码都需要手动编译,非常麻烦。
webpack 中有几个不同的选项,可以帮助你在代码发生变化后自动编译代码:
- webpack's Watch Mode
 - webpack-dev-server
 - webpack-dev-middleware
 
我们这里主要讲解用得比较多的webpack-dev-server,如果对其他两种方式感兴趣的可以自行去了解。
webpack-dev-server 为你提供了一个简单的 web 服务器,并且能够实时重新加载(live reloading)。我们做以下设置:
npm install --save-dev webpack-dev-server
webpack.config.js
const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');
  const CleanWebpackPlugin = require('clean-webpack-plugin');
  module.exports = {
    entry: {
      app: './src/index.js',
      print: './src/print.js'
    },
    devtool: 'inline-source-map',
+   devServer: {
+     contentBase: './dist'
+   },
    plugins: [
      new CleanWebpackPlugin(['dist']),
      new HtmlWebpackPlugin({
        title: 'Development'
      })
    ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };
以上配置告知 webpack-dev-server,在 localhost:8080 下建立服务,将 dist 目录下的文件,作为可访问文件。
让我们添加一个 script 脚本,可以直接运行开发服务器(dev server):
package.json
{
    "name": "development",
    "version": "1.0.0",
    "description": "",
    "main": "webpack.config.js",
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",
      "watch": "webpack --watch",
+     "start": "webpack-dev-server --open",
      "build": "webpack"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "devDependencies": {
      "clean-webpack-plugin": "^0.1.16",
      "css-loader": "^0.28.4",
      "csv-loader": "^2.1.1",
      "file-loader": "^0.11.2",
      "html-webpack-plugin": "^2.29.0",
      "style-loader": "^0.18.2",
      "webpack": "^3.0.0",
      "xml-loader": "^1.2.1"
    }
  }
现在,我们可以在命令行中运行 npm start,就会看到浏览器自动加载页面。如果现在修改和保存任意源文件,web 服务器就会自动重新加载编译后的代码。试一下!
webpack从零的实践(新手良药)的更多相关文章
- [vue]webpack&vue组件工程化实践
		
[vue]全局组件和局部组件(嵌套+props引用父组件数据) [vue]组件篇 [vue]组件的创建(componet)和销毁(keep-alive缓存)和父子dom同步nextTick [vue] ...
 - 使用vue+webpack从零搭建项目
		
vue到现在已经成为一个热门的框架,在项目实践当中,如果想要创建一个新项目,通常都会使用vue-cli的脚手架工具,毋容置疑能够方便很多,很多东西也不需要自己亲自去配置.都知道,脚手架其实是vue结合 ...
 - Java学习之SpringMVC零配置实践
		
概述:本实践主要是对SpringMVC的主要功能做了一个大概的体验,将原来的SpringMVC的大量配置改成用SpringBoot进行集成,做到了零XML配置,本次实践分为两个部分,一部分为基本功能实 ...
 - vue-cli3 vue2 保留 webpack 支持 vite 成功实践
		
大家好! 文本是为了提升开发效率及体验实践诞生的. 项目背景: 脚手架:vue-cli3,具体为 "@vue/cli-service": "^3.4.1" 库: ...
 - 作为一个零基础的新手,如何系统的自学Java和JavaEE开发技术?
		
其实这个问题很简单,我用最简单的语言给大家描述一下,学习一样东西就要了解这样东西学完了要干什么事情,有什么作用.然后就是应该学习哪些必要的内容,该如何运用得当的方法进行有效率的学习不至于自己摸不着头脑 ...
 - webpack 入门总结和实践(按需异步加载,css单独打包,生成多个入口文件)
		
为什么是webpack webpack一下自己就
 - webpack + react 前端工程化实践和暂不极致优化
		
技术结构 webpack + react + react-router 功能实现 关于打包 1.基于react-router的自定义打包code split.2.分包异步按需加载.3.CommonsC ...
 - ABAP system landscape和vue项目webpack构建的最佳实践
		
基于Netweaver的ABAP transport route一般都有dev,test和prod三种类型的系统. 而Vue前端项目的webpack build设置也类似. 以SAP成都研究院数字创新 ...
 - 对webpack从零配置
		
一.新建配置文件,文件名一般为webpack.config.js: 二.配置文件目录,一般为根目录,一般会放在./build文件夹下 三.配置文件格式一般为module.exports={}, 四.结 ...
 
随机推荐
- JDBC 进阶:使用封装通用DML DQL 和结构分层以及at com.mysql.jdbc.PreparedStatement.setTimestamp空指针异常解决
			
准备: 数据表 CREATE TABLE `t_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(10) DEFAULT ...
 - Java 多线程实现方式二:实现 Runnable 接口
			
由于java是单继承,很多时候为了实现多线程 通过继承 Thread 类后,就不能再继承其他类了.为了方便可以通过实现 Runnable 接口来实现,和Tread 类似需要重写run 方法. 下面通过 ...
 - 引入OpenCV导致私有内存巨大
			
引入OpenCV导致私有内存巨大 opencvC++VS2015 说明 在调试程序的时候 发现自己的程序在VS的调试窗口占用很高, 花时间关注了一下这个问题, 手动写了小的程序复现这个问题,最终确定了 ...
 - Linux网络服务第三章远程访问及控制
			
1.笔记 655355:端口限制 监听地址:对外提供服务的地址 AllowUsers:仅允许用户登录 DenyUsers:仅禁止用户登录 AllowUsers-用户名-公网地址 ssh/id_rsa. ...
 - (数据科学学习手札82)基于geopandas的空间数据分析——geoplot篇(上)
			
本文示例代码和数据已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 在前面的基于geopandas的空间数据分 ...
 - 如何把字符串数组从 Swift 传递给 C
			
作者:Natasha The Robot,原文链接,原文日期:2016-10-27译者:BigbigChai:校对:walkingway:定稿:CMB Swift 允许我们将原生的字符串直接传递给一个 ...
 - ElementUI表单验证攻略:解决表单项启用和禁用验证的切换,以及动态表单验证的综合性问题
			
试想一种比较复杂的业务场景: 表格(el-table)的每一行数据的第一列是勾选框,最后一列是输入框.当某一行的勾选框勾上时,启用该行的输入框,并开启该行输入框的表单验证:取消该行的勾选框,则禁用该行 ...
 - Java反射详细介绍
			
反射 目录介绍 1.反射概述 1.1 反射概述 1.2 获取class文件对象的三种方式 1.3 反射常用的方法介绍 1.4 反射的定义 1.5 反射的组成 1.6 反射的作用有哪些 2.反射的相关使 ...
 - 图论--BFS总结
			
1.关于BFS的Key_word: ①hash或状态压缩记录状态 ②状态剪枝 ③反向BFS ④双向BFS ⑤特殊初始化VIS数组 ⑥动态图的搜索 ⑦优先队列优化搜索 ⑧数位搜索 下面是一一讲解: 1 ...
 - django+nginx+uwsgi的生产环境部署(Ubuntu16.04)
			
一,准备工作: 代码一定要能本地跑起来! 各种基础包的安装略默认已经安装python3,nginx,uwsgi等基础依赖,注意版本问题. 本地setting.py文件修改如下(改为生产模式,把debu ...