Let me preface this section by saying that webpack is hard and will be confusing if this is your first experience with a code bundler. If you have experience with other tools like Gulp or Grunt it's a bit easier, but if you don't, just hold on and it will get easier. We're going to hyper-focus on the real meat of webpack for this course and jump into more advanced features in other courses.
The first questions you should ask yourself whenever you're using a new tool are "Why does this thing exist?" and "What problem is this thing solving?". If you can't eventually answer those two questions, you probably don't need it. So let's answer those questions in terms of webpack.
为什么存在
Why does webpack exist? webpack, at its core, is a code bundler. It takes your code, transforms and bundles it, then returns a brand new version of your code.
解决了什么问题
What problem is this thing solving?
Think about how many times we have to take our code and change it so it's compliant with what the browser is used to (vanilla HTML, CSS, and JavaScript). If you've ever used a CSS Preprocessor like SASS or LESS you know you need to transform your SASS/LESS code into normal CSS. If you've ever used CoffeeScript or any compile to JavaScript language you know there's also a conversion step there. So where webpack really shines is you're able to tell it every transformation your code needs to make, and it will do them and output a bundle file for you full of those changes (and some other helpful things as well like minification if you desire).
怎么做
If you think about the process we talked about above, that idea of taking your code and transforming it in someway then spitting it out - there are really three main steps and three main things webpack needs to know.
入口
- webpack needs to know the starting point of your application, or your root JavaScript file.
如何转化
- webpack needs to know which transformations to make on your code.
出口
- webpack needs to know to which location it should save the new transformed code.
The code for this might look a little scary at first, but know that all we're doing is coding out those three steps above so webpack can understand them.
Reminder: Text sections are just supposed to be read, not coded along with. So absorb as much as you can then in the video we'll walk through creating our webpack.config.js file together.
The first thing we need to do is create a file which is going to contain our webpack configurations. Conveniently, this file should be named webpack.config.js and be located in the root directory of our project.
Now that we have our file made we need to make sure that this file exports an object which is going to represent our configurations for webpack. If you're not familiar with JavaScript modules, this blog series is great.
// In webpack.config.js
module.exports = {}
Now let's go ahead and walk down our list of those three items and convert them into code. First up, telling webpack where the entry point of our application is located. This one is pretty straight forward and looks like this,
// In webpack.config.js
module.exports = {
entry: './app/index.js',
}
All we do is give our object a property of entry and a value which is a string which points to our root JavaScript file in our app.
Now that we've told webpack where to start, we need to tell it which transformations to actually make. This is where loaders will come in handy.
Let's say that for some reason we're still writing CoffeeScript in 2017 (ZING!). We would need a way to transform our CoffeeScript into regular JS. Sounds like the perfect place for a CoffeeScript loader. First step, we need to install the loader we want. We'll use npm to do this. In your terminal you would run npm install --save-dev coffee-loader which would then save coffee-loader as a dev dependency in your package.json file. Next, we need to configure our webpack.config.js file to be aware of this specific transform. To do that we'll first need to add a module property to the object we're exporting in webpack.config.js and then that module property will have a property of rules property which is an array.
// In webpack.config.js
// In webpack.config.js
module.exports = {
entry: [
'./app/index.js'
],
module: {
rules: []
},
}
Inside of that rules array is where we're going to put all of our different loaders or transformations we want to take place.
Each loader needs to be composed of two things. The first is which file type to run the specific transformation on. For example, we don't want to run CSS transformations on a JavaScript file and vice versa. The second thing is the specific loader we want to run. Let's take a look at what this looks like.
// In webpack.config.js
module.exports = {
entry:
'./app/index.js',
module: { rules: [ { test: /\.coffee$/, use: "coffee-loader" } ]
},
}
The first thing you'll probably notice is the test:/.coffee$/ section. It looks scary if you're not used to regular expressions but all it's doing is it tells webpack to run the coffee-loader on all extensions that end in .coffee.
Next step, use tells webpack which transformation to run on all paths that match the test RegEx.
As you can see, the steps for including more loaders is pretty basic. NPM install the specific loader then add a new object to the rules array.
Now that we've done steps 1 and 2, we just have one more step. This last step is specifying where webpack should output the new transformed code.
// In webpack.config.js
module.exports = {
entry: './app/index.js',
module: {
rules: [
{ test: /\.coffee$/, use: "coffee-loader" }
]
},
}
Again the code here is pretty self explanatory. filename is the name of the file that webpack is going to create which contains our new transformed code. path is the specific directory where the new filename (index_bundle.js) is going to be placed. If you've never seen __dirname before that's just referencing the name of the directory that the currently executing script resides in.
So now when webpack runs, our code will be transformed and then can be referenced at ourApp/dist/index_bundle.js. Well that's great, but now we need to come up with a plan to get our HTML to reference this specific file. There are a few options, but most are crappy. If we look at how normal apps are usually structured it's usually something like this.
/app
- components
- utils
index.js
index.html
/dist
index.html
index_bundle.js
package.json
webpack.config.js
.gitignore
So as you can see, our code we're developing with is found in the app folder and our transformed code is in the dist folder. Now you can visually see the issue. We want to change the index.html located in the app folder but the index.html file that the browser is actually going to be using is located in the dist folder (because that's where we've also told webpack to spit out the transformed JS file).
The first option to solve this is to just manage two index.html files and whenever you change the one located in /app, copy/paste that to the one located in /dist. Though this will work, I won't be able to look my Wife in the eyes again if we do this.
Second option, we could figure out a way so that whenever webpack runs, our /app/index.html gets copied over to /dist/index.html. This sounds a lot better and the final solution will look close to this.
As you can probably guess, there's already a Webpack tool that allows us to do something similar. Instead of actually copying our index.html file, it's just going to use that file as a template and create a brand new index.html file. This plugin is the html-webpack-plugin. As always, you'll need to run npm install --save-dev html-webpack-plugin before you can use it. Now we just need to tell webpack what we want to do with it.
First thing, we'll need to create a new instance of HTMLWebpackPlugin and specify one thing, the template of what we want the newly created file to look like.
Let's see what all the code looks like.
// In webpack.config.js
module.exports = {
entry: './app/index.js',
module: {
rules: [
{ test: /\.coffee$/, use: "coffee-loader" }
]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index_bundle.js'
},
plugins: [
new HtmlWebpackPlugin({
template: 'app/index.html'
})
]
}
You'll notice we've added a plugins property on our object and the first item in the array is a new instance of HtmlWebpackPlugin with template pointing to our regular index.html file located in our app directory.
Now if we run webpack from our command line, inside of our dist folder we'll have two files. index_bundle.js and index.html. index_bundle.js is the result of taking our entry code and running it through our loaders. While index.html was created on the fly with HTMLWebpackPluginConfig and is a copy of our original index.html file located in our app folder with a script tag referencing the newly created index_bundle.js file
Let's take a look at what our index.html file inside of our app folder looks like then what our newly created index.html file inside of dist looks like.
app/index.html
My App
dist/index.html
My App
You'll notice that the only difference between the two files is that the one in dist (which was created with HTMLWebpackPlugin) now has a script tag pointing to index_bundle.js . Again, the only real magic going on here is that HTMLWebpackConfig is smart enough to detect the output filename of your newly created file from Webpack and it will automatically add that as a script in your newly created index.html file. So in our example we used index_bundle.js as the output filename so as you can see in the created index.html file above, we have now have inside the body. If we were to change the output of our webpack config to be OUR-AWESOME-JS-FILE.js, then inside the body of our newly create index.html file we would have
Finally, as of Webpack 4, back in our webpack.config.js file we need to tell it which "mode" we want it to run in - "production" or "development". For now, we'll just set the mode to "development". This will enable things like tooling for debugging and faster builds.
// In webpack.config.js
module.exports = {
entry: './app/index.js',
module: {
rules: [
{ test: /\.coffee$/, use: "coffee-loader" }
]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index_bundle.js'
},
plugins: [
new HtmlWebpackPlugin({
template: 'app/index.html'
})
],
mode: "development"
}
Now the only other crucial piece of information is how to actually tell webpack to run.
In order to do that, you'll need to install two things. "webpack" and "webpack-cli". Once installed, you'll be able to add a new script to your package.json which runs webpack.
package.json
"scripts": {
"build": "webpack"
},
Now in your terminal you can run "npm run build" which will do a one time run through of your settings then compile your code and output into a dist folder. However, this can be kind of a pain to keep having to run the command over and over whenever you change anything. To fix this, change webpack in your NPM script to run webpack -w and that will watch your files and re-execute webpack whenever any of the files Webpack is concerned about changes. Lastly, if you're wanting to ship to production, you can run webpack -p and that will run through the normal transformations as well as minify your code (we'll do this in the last video of the course).
So now you're probably thinking, "Hey Tyler. That's cool and all, but I came here for React and I still haven't seen any of it." First off, calm down. Second, that's fair. The good news is you already have all the webpack knowledge you need to get going with React. Now we just need to look at a tool called babel and add it as a loader.
Babel.js is a wonderful tool for compiling your JavaScript. With Webpack you tell it which transformations to make on your code, while Babel is the specific transformation itself. In terms of React, Babel is going to allow us to transform our JSX (which you'll see soon) into actual JavaScript. What's nice about Babel though is it can be used for much more than just JSX -> JS transformations. You can also opt into "future" versions of JavaScript (ES2015, 2016, etc) and use Babel to transform your future JavaScript to modern day JavaScript so the browser can understand it. The overview of implementing Babel is that we're going to npm install a bunch of tools specific to Babel, then we'll add babel as a loader in our webpack settings. Then whenever webpack bundles our code, it'll transform our code with whatever we specified with Babel. You'll see the specific details of this in the next video.
That's it! I realize that's a lot of steps but you've probably realized that the code for these steps isn't actually difficult, it's just piecing everything together that is.
Webpack 4 设置
yarn init
yarn add react react-dom
yarn add babel-core babel-loader babel-preset-env babel-preset-react css-loader style-loader html-webpack-plugin webpack webpack-dev-server webpack-cli --dev
// package.json 加上
"babel": {
"presets": ["env", "react"]
},
// 一个 package.json 的例子
{
"name": "try",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"babel": {
"presets": ["env", "react"]
},
"devDependencies": {
"babel-core": "^6.26.3",
"babel-loader": "^7.1.4",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"css-loader": "^0.28.11",
"html-webpack-plugin": "^3.2.0",
"style-loader": "^0.21.0",
"webpack": "^4.8.3",
"webpack-cli": "^2.1.3",
"webpack-dev-server": "^3.1.4"
},
"scripts": {
"start": "webpack --open",
"dev": "webpack-dev-server --open --hot"
},
"dependencies": {
"react": "^16.3.2",
"react-dom": "^16.3.2"
}
}
// you need to tell Webpack which "mode" you want to run in: "production" or "development".
// 入口创建一个 app 文件夹, 建立 index.js 的文件作为入口文件
// In webpack.config.js
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './app/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index_bundle.js'
},
module: {
rules: [
{ test: /\.(js)$/, use: 'babel-loader' },
{ test: /\.css$/, use: [ 'style-loader', 'css-loader' ]}
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'app/index.html'
})
],
mode: "development"
};