I prefer the traditional lexer -> parser -> optimization -> code generator path.
If I were going to change anything about teaching compilers, it would be to teach recursive descent, which makes the recursive nature of grammars a lot more obvious than parser generator approaches do.
This is a great book/website a contributor to Dart, which explains the algorithm: